Files
tutti/services/tuttid/api/daemon_agent_sessions.go
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

806 lines
35 KiB
Go

package api
import (
"context"
"fmt"
"log/slog"
"strings"
"time"
"github.com/tutti-os/tutti/packages/agent/daemon/providerregistry"
agenthost "github.com/tutti-os/tutti/packages/agent/host"
agentactivitybiz "github.com/tutti-os/tutti/packages/agent/store-sqlite"
"github.com/tutti-os/tutti/packages/agent/store-sqlite/canonical"
tuttigenerated "github.com/tutti-os/tutti/services/tuttid/api/generated"
"github.com/tutti-os/tutti/services/tuttid/apierrors"
preferencesbiz "github.com/tutti-os/tutti/services/tuttid/biz/preferences"
agentservice "github.com/tutti-os/tutti/services/tuttid/service/agent"
)
type AgentSessionService interface {
List(context.Context, string) ([]agentservice.Session, error)
ListFiltered(context.Context, string, agentservice.ListSessionsInput) ([]agentservice.Session, error)
ListPage(context.Context, string, agentservice.ListSessionsInput) (agentservice.SessionListPage, error)
ListSessionSections(context.Context, string, agentservice.ListSessionSectionsInput) (agentservice.SessionSectionsPage, error)
ListSessionSectionPage(context.Context, string, agentservice.ListSessionSectionPageInput) (agentservice.SessionSection, error)
ListSessionSectionDeletionCandidates(context.Context, string, agentservice.ListSessionSectionDeletionCandidatesInput) (agentservice.SessionSectionDeletionCandidates, error)
DeleteSessionsBatch(context.Context, string, agentservice.DeleteSessionsBatchInput) (agentservice.DeleteSessionsBatchResult, error)
ListPinnedSessionPage(context.Context, string, agentservice.ListPinnedSessionPageInput) (agentservice.SessionPage, error)
GetComposerOptions(context.Context, agentservice.ComposerOptionsInput) (agentservice.ComposerOptions, error)
ListGeneratedFiles(context.Context, string, agentservice.ListGeneratedFilesInput) (agentservice.GeneratedFileList, error)
ListMessages(context.Context, string, string, agentservice.ListMessagesInput) (agentservice.SessionMessagesPage, error)
ScanExternalImports(context.Context, agentservice.ExternalImportScanInput) (agentservice.ExternalImportScanResult, error)
ImportExternalSessions(context.Context, string, agentservice.ExternalImportInput) (agentservice.ExternalImportResult, error)
ExternalImportValidProjectPaths(context.Context, agentservice.ExternalImportInput) ([]string, error)
Create(context.Context, string, agentservice.CreateSessionInput) (agentservice.Session, error)
Fork(context.Context, string, string, agentservice.ForkSessionInput) (agentservice.SessionForkOperation, error)
GetSessionForkOperation(context.Context, string, string) (agentservice.SessionForkOperation, error)
AcknowledgeSessionForkOperation(context.Context, string, string) (agentservice.SessionForkOperation, error)
Get(context.Context, string, string) (agentservice.Session, error)
GetDetailWithProjection(context.Context, string, string, agentservice.SessionDetailProjection) (agentservice.SessionDetail, error)
ReadAttachment(context.Context, string, string, string) (agentservice.PromptAttachment, error)
ListGitBranches(context.Context, string, string) (agentservice.GitBranches, error)
ListGitBranchesForPath(context.Context, string, string) (agentservice.GitBranches, error)
ResolveGitPatchSupportForPath(context.Context, string, string) (agentservice.GitPatchSupport, error)
ResolveSessionWorktreeSupport(context.Context, string, string, string) (agentservice.SessionWorktreeSupport, error)
ApplyGitPatchForPath(context.Context, string, agentservice.ApplyGitPatchInput) (agentservice.ApplyGitPatchResult, error)
Clear(context.Context, string) (agentservice.ClearSessionsResult, error)
Delete(context.Context, string, string) (agentservice.DeleteSessionResult, error)
CancelTurn(context.Context, string, string, string) (agentservice.CancelTurnResult, error)
GoalControl(context.Context, agentservice.GoalControlInput) (agentservice.GoalControlSessionResult, error)
GetGoalState(context.Context, string, string) (agentservice.GoalStateSessionResult, error)
ReconcileGoal(context.Context, string, string) (agentservice.GoalStateSessionResult, error)
SendInput(context.Context, string, string, agentservice.SendInput) (agentservice.SendInputResult, error)
UpdatePin(context.Context, string, string, bool) (agentservice.Session, error)
UpdateTitle(context.Context, string, string, string) (agentservice.Session, error)
UpdateVisible(context.Context, string, string, bool) (agentservice.Session, error)
UpdateSettings(context.Context, string, string, agentservice.ComposerSettingsPatch) (agentservice.Session, error)
SubmitInteractive(context.Context, agenthost.InteractionRef, agenthost.SubmitInteractiveInput) (agentservice.Session, error)
}
func (api DaemonAPI) ClearWorkspaceAgentSessions(ctx context.Context, request tuttigenerated.ClearWorkspaceAgentSessionsRequestObject) (tuttigenerated.ClearWorkspaceAgentSessionsResponseObject, error) {
if api.AgentSessionService == nil {
return tuttigenerated.ClearWorkspaceAgentSessions503JSONResponse{
ServiceUnavailableErrorJSONResponse: agentSessionServiceUnavailableError(),
}, nil
}
result, err := api.AgentSessionService.Clear(ctx, string(request.WorkspaceID))
if err != nil {
return writeClearWorkspaceAgentSessionsError(err), nil
}
return tuttigenerated.ClearWorkspaceAgentSessions200JSONResponse{
RemovedMessages: result.RemovedMessages,
RemovedSessions: result.RemovedSessions,
CleanupFailedSessionIds: append([]string{}, result.CleanupFailedSessionIDs...),
}, nil
}
func (api DaemonAPI) GetAgentProviderComposerOptions(ctx context.Context, request tuttigenerated.GetAgentProviderComposerOptionsRequestObject) (tuttigenerated.GetAgentProviderComposerOptionsResponseObject, error) {
if api.AgentSessionService == nil {
return tuttigenerated.GetAgentProviderComposerOptions503JSONResponse{
ServiceUnavailableErrorJSONResponse: agentSessionServiceUnavailableError(),
}, nil
}
input := agentservice.ComposerOptionsInput{
Provider: string(request.Provider),
}
if request.Body != nil {
input.AgentSessionID = optionalStringValue(request.Body.AgentSessionId)
if request.Body.Section != nil {
input.Section = agentservice.ComposerOptionsSection(*request.Body.Section)
}
input.AgentTargetID = optionalStringValue(request.Body.AgentTargetId)
input.Cwd = optionalStringValue(request.Body.Cwd)
input.WorkspaceID = optionalStringValue(request.Body.WorkspaceId)
input.WaitForFreshModelCatalog = request.Body.WaitForFreshModelCatalog != nil && *request.Body.WaitForFreshModelCatalog
}
if request.Body != nil && request.Body.Settings != nil {
input.Settings = composerSettingsFromGenerated(*request.Body.Settings)
input.CodexSaverMode = request.Body.Settings.CodexSaverMode
input.RTKSaverMode = request.Body.Settings.RtkSaverMode
}
if request.Body != nil && request.Body.Locale != nil {
input.Locale = string(*request.Body.Locale)
} else {
input.Locale = api.composerDefaultLocale(ctx)
}
options, err := api.AgentSessionService.GetComposerOptions(ctx, input)
if err != nil {
return writeGetAgentProviderComposerOptionsError(err), nil
}
return tuttigenerated.GetAgentProviderComposerOptions200JSONResponse(
generatedAgentProviderComposerOptions(options),
), nil
}
func (api DaemonAPI) GetWorkspaceAgentSession(ctx context.Context, request tuttigenerated.GetWorkspaceAgentSessionRequestObject) (tuttigenerated.GetWorkspaceAgentSessionResponseObject, error) {
if request.Params.Projection != nil && !request.Params.Projection.Valid() {
return tuttigenerated.GetWorkspaceAgentSession400JSONResponse{
InvalidRequestErrorJSONResponse: invalidRequestError(
apierrors.InvalidRequest(
apierrors.ReasonMalformedRequest,
apierrors.WithDeveloperMessage("unsupported agent session detail projection"),
),
),
}, nil
}
if api.AgentSessionService == nil {
return tuttigenerated.GetWorkspaceAgentSession503JSONResponse{
ServiceUnavailableErrorJSONResponse: agentSessionServiceUnavailableError(),
}, nil
}
projection := agentservice.SessionDetailProjectionFull
if request.Params.Projection != nil &&
*request.Params.Projection == tuttigenerated.MessageHydration {
projection = agentservice.SessionDetailProjectionMessageHydration
}
detail, err := api.AgentSessionService.GetDetailWithProjection(
ctx,
string(request.WorkspaceID),
string(request.AgentSessionID),
projection,
)
if err != nil {
return writeGetWorkspaceAgentSessionError(err), nil
}
generatedSession, err := generatedAgentSession(detail.Session)
if err != nil {
return writeGetWorkspaceAgentSessionError(err), nil
}
generatedChildren, err := generatedAgentSessions(detail.ChildSessions)
if err != nil {
return writeGetWorkspaceAgentSessionError(err), nil
}
return tuttigenerated.GetWorkspaceAgentSession200JSONResponse{
Session: generatedSession,
ChildSessions: generatedChildren,
Turns: generatedAgentTurns(detail.Turns),
Projection: tuttigenerated.WorkspaceAgentSessionDetailProjection(projection),
LifecycleCapabilitiesProjected: projection == agentservice.SessionDetailProjectionFull,
EditRetry: generatedAgentEditRetryAvailability(detail.EditRetry),
}, nil
}
func generatedAgentTurns(turns []agentactivitybiz.Turn) []tuttigenerated.WorkspaceAgentTurn {
result := make([]tuttigenerated.WorkspaceAgentTurn, 0, len(turns))
for _, turn := range turns {
result = append(result, agentservice.GeneratedWorkspaceAgentTurn(turn))
}
return result
}
func (api DaemonAPI) DeleteWorkspaceAgentSession(ctx context.Context, request tuttigenerated.DeleteWorkspaceAgentSessionRequestObject) (tuttigenerated.DeleteWorkspaceAgentSessionResponseObject, error) {
if api.AgentSessionService == nil {
return tuttigenerated.DeleteWorkspaceAgentSession503JSONResponse{
ServiceUnavailableErrorJSONResponse: agentSessionServiceUnavailableError(),
}, nil
}
result, err := api.AgentSessionService.Delete(ctx, string(request.WorkspaceID), string(request.AgentSessionID))
if err != nil {
return writeDeleteWorkspaceAgentSessionError(err), nil
}
return tuttigenerated.DeleteWorkspaceAgentSession200JSONResponse{
Removed: result.Removed, CleanupFailed: result.CleanupFailed,
}, nil
}
func (api DaemonAPI) ListWorkspaceAgentSessionMessages(ctx context.Context, request tuttigenerated.ListWorkspaceAgentSessionMessagesRequestObject) (tuttigenerated.ListWorkspaceAgentSessionMessagesResponseObject, error) {
if api.AgentSessionService == nil {
return tuttigenerated.ListWorkspaceAgentSessionMessages503JSONResponse{
ServiceUnavailableErrorJSONResponse: agentSessionServiceUnavailableError(),
}, nil
}
startedAt := time.Now()
workspaceID := string(request.WorkspaceID)
agentSessionID := string(request.AgentSessionID)
input := agentservice.ListMessagesInput{}
if request.Params.AfterVersion != nil {
afterVersion, err := workspaceAgentMessageCursorFromRequest(*request.Params.AfterVersion)
if err != nil {
return writeListWorkspaceAgentSessionMessagesError(agentservice.ErrInvalidArgument), nil
}
input.AfterVersion = afterVersion
}
if request.Params.BeforeVersion != nil {
beforeVersion, err := workspaceAgentMessageCursorFromRequest(*request.Params.BeforeVersion)
if err != nil {
return writeListWorkspaceAgentSessionMessagesError(agentservice.ErrInvalidArgument), nil
}
input.BeforeVersion = beforeVersion
}
if request.Params.Order != nil {
switch *request.Params.Order {
case tuttigenerated.Asc:
input.Order = agentactivitybiz.MessageOrderAsc
case tuttigenerated.Desc:
input.Order = agentactivitybiz.MessageOrderDesc
default:
return writeListWorkspaceAgentSessionMessagesError(agentservice.ErrInvalidArgument), nil
}
}
if request.Params.Limit != nil {
if *request.Params.Limit <= 0 {
return writeListWorkspaceAgentSessionMessagesError(agentservice.ErrInvalidArgument), nil
}
input.Limit = *request.Params.Limit
}
slog.Debug("workspace agent session messages list requested",
"event", "workspace.agent_session.messages.api.list_requested",
"workspace_id", workspaceID,
"agent_session_id", agentSessionID,
"after_version", input.AfterVersion,
"before_version", input.BeforeVersion,
"order", input.Order,
"limit", input.Limit,
)
page, err := api.AgentSessionService.ListMessages(
ctx,
workspaceID,
agentSessionID,
input,
)
if err != nil {
slog.Warn("workspace agent session messages list failed",
"event", "workspace.agent_session.messages.api.list_failed",
"workspace_id", workspaceID,
"agent_session_id", agentSessionID,
"after_version", input.AfterVersion,
"before_version", input.BeforeVersion,
"order", input.Order,
"limit", input.Limit,
"duration_ms", time.Since(startedAt).Milliseconds(),
"error", err,
)
return writeListWorkspaceAgentSessionMessagesError(err), nil
}
messages, err := generatedAgentSessionMessages(page.Messages)
var latestVersion int64
if err == nil {
latestVersion, err = generatedWorkspaceAgentSafeInteger("latest message version", page.LatestVersion)
}
if err != nil {
firstVersion, lastVersion := agentSessionMessageVersionRange(page.Messages)
slog.Warn("workspace agent session messages response transform failed",
"event", "workspace.agent_session.messages.api.transform_failed",
"workspace_id", workspaceID,
"agent_session_id", agentSessionID,
"after_version", input.AfterVersion,
"before_version", input.BeforeVersion,
"order", input.Order,
"limit", input.Limit,
"message_count", len(page.Messages),
"first_version", firstVersion,
"last_version", lastVersion,
"latest_version", page.LatestVersion,
"has_more", page.HasMore,
"duration_ms", time.Since(startedAt).Milliseconds(),
"error", err,
)
return writeListWorkspaceAgentSessionMessagesError(err), nil
}
firstVersion, lastVersion := generatedAgentSessionMessageVersionRange(messages)
slog.Debug("workspace agent session messages list completed",
"event", "workspace.agent_session.messages.api.list_completed",
"workspace_id", workspaceID,
"agent_session_id", agentSessionID,
"after_version", input.AfterVersion,
"before_version", input.BeforeVersion,
"order", input.Order,
"limit", input.Limit,
"message_count", len(messages),
"first_version", firstVersion,
"last_version", lastVersion,
"latest_version", page.LatestVersion,
"has_more", page.HasMore,
"duration_ms", time.Since(startedAt).Milliseconds(),
)
return tuttigenerated.ListWorkspaceAgentSessionMessages200JSONResponse{
AgentSessionId: page.AgentSessionID,
HasMore: page.HasMore,
LatestVersion: latestVersion,
Messages: messages,
}, nil
}
func (api DaemonAPI) ListWorkspaceAgentGeneratedFiles(ctx context.Context, request tuttigenerated.ListWorkspaceAgentGeneratedFilesRequestObject) (tuttigenerated.ListWorkspaceAgentGeneratedFilesResponseObject, error) {
if api.AgentSessionService == nil {
return tuttigenerated.ListWorkspaceAgentGeneratedFiles503JSONResponse{
ServiceUnavailableErrorJSONResponse: agentSessionServiceUnavailableError(),
}, nil
}
input := agentservice.ListGeneratedFilesInput{}
input.SectionKey = strings.TrimSpace(request.Params.SectionKey)
if request.Params.Query != nil {
input.Query = strings.TrimSpace(*request.Params.Query)
}
if request.Params.AgentTargetIds != nil {
if len(*request.Params.AgentTargetIds) > agentservice.MaxGeneratedFileAgentTargetFilters {
return writeListWorkspaceAgentGeneratedFilesError(agentservice.ErrInvalidArgument), nil
}
input.AgentTargetIDs = append([]string(nil), (*request.Params.AgentTargetIds)...)
}
if request.Params.Cursor != nil {
input.Cursor = strings.TrimSpace(*request.Params.Cursor)
}
if request.Params.Limit != nil {
if *request.Params.Limit <= 0 || *request.Params.Limit > 100 {
return writeListWorkspaceAgentGeneratedFilesError(agentservice.ErrInvalidArgument), nil
}
input.Limit = *request.Params.Limit
}
result, err := api.AgentSessionService.ListGeneratedFiles(
ctx,
string(request.WorkspaceID),
input,
)
if err != nil {
return writeListWorkspaceAgentGeneratedFilesError(err), nil
}
response := tuttigenerated.ListWorkspaceAgentGeneratedFiles200JSONResponse{
Entries: generatedAgentGeneratedFiles(result.Files),
HasMore: result.HasMore,
WorkspaceId: result.WorkspaceID,
}
if result.NextCursor != "" {
response.NextCursor = &result.NextCursor
}
return response, nil
}
func (api DaemonAPI) ReadWorkspaceAgentSessionAttachment(ctx context.Context, request tuttigenerated.ReadWorkspaceAgentSessionAttachmentRequestObject) (tuttigenerated.ReadWorkspaceAgentSessionAttachmentResponseObject, error) {
if api.AgentSessionService == nil {
return tuttigenerated.ReadWorkspaceAgentSessionAttachment503JSONResponse{
ServiceUnavailableErrorJSONResponse: agentSessionServiceUnavailableError(),
}, nil
}
attachment, err := api.AgentSessionService.ReadAttachment(
ctx,
string(request.WorkspaceID),
string(request.AgentSessionID),
string(request.AttachmentID),
)
if err != nil {
return writeReadWorkspaceAgentSessionAttachmentError(err), nil
}
return tuttigenerated.ReadWorkspaceAgentSessionAttachment200JSONResponse{
AttachmentId: attachment.AttachmentID,
MimeType: tuttigenerated.WorkspaceAgentSessionAttachmentResponseMimeType(attachment.MimeType),
Data: attachment.Data,
}, nil
}
func (api DaemonAPI) ListWorkspaceAgentSessionGitBranches(ctx context.Context, request tuttigenerated.ListWorkspaceAgentSessionGitBranchesRequestObject) (tuttigenerated.ListWorkspaceAgentSessionGitBranchesResponseObject, error) {
if api.AgentSessionService == nil {
return tuttigenerated.ListWorkspaceAgentSessionGitBranches503JSONResponse{
ServiceUnavailableErrorJSONResponse: agentSessionServiceUnavailableError(),
}, nil
}
branches, err := api.AgentSessionService.ListGitBranches(ctx, string(request.WorkspaceID), string(request.AgentSessionID))
if err != nil {
return writeListWorkspaceAgentSessionGitBranchesError(err), nil
}
response := tuttigenerated.ListWorkspaceAgentSessionGitBranches200JSONResponse{Branches: branches.Branches}
if response.Branches == nil {
response.Branches = []string{}
}
if branches.CurrentBranch != "" {
current := branches.CurrentBranch
response.CurrentBranch = &current
}
return response, nil
}
func (api DaemonAPI) ListWorkspaceGitBranches(ctx context.Context, request tuttigenerated.ListWorkspaceGitBranchesRequestObject) (tuttigenerated.ListWorkspaceGitBranchesResponseObject, error) {
if api.AgentSessionService == nil {
return tuttigenerated.ListWorkspaceGitBranches503JSONResponse{
ServiceUnavailableErrorJSONResponse: agentSessionServiceUnavailableError(),
}, nil
}
branches, err := api.AgentSessionService.ListGitBranchesForPath(ctx, string(request.WorkspaceID), request.Params.WorkingDirectory)
if err != nil {
return writeListWorkspaceGitBranchesError(err), nil
}
response := tuttigenerated.ListWorkspaceGitBranches200JSONResponse{Branches: branches.Branches}
if response.Branches == nil {
response.Branches = []string{}
}
if branches.CurrentBranch != "" {
current := branches.CurrentBranch
response.CurrentBranch = &current
}
return response, nil
}
func (api DaemonAPI) SubmitWorkspaceAgentInteractive(ctx context.Context, request tuttigenerated.SubmitWorkspaceAgentInteractiveRequestObject) (tuttigenerated.SubmitWorkspaceAgentInteractiveResponseObject, error) {
if api.AgentSessionService == nil {
return tuttigenerated.SubmitWorkspaceAgentInteractive503JSONResponse{
ServiceUnavailableErrorJSONResponse: agentSessionServiceUnavailableError(),
}, nil
}
if request.Body == nil {
return tuttigenerated.SubmitWorkspaceAgentInteractive400JSONResponse{
InvalidRequestErrorJSONResponse: invalidRequestError(apierrors.EmptyBody(apierrors.WithDeveloperMessage("empty body"))),
}, nil
}
session, err := api.AgentSessionService.SubmitInteractive(ctx, agenthost.InteractionRef{
WorkspaceID: string(request.WorkspaceID), AgentSessionID: string(request.AgentSessionID),
TurnID: request.Body.TurnId, RequestID: string(request.RequestID),
}, agenthost.SubmitInteractiveInput{
Action: request.Body.Action,
OptionID: request.Body.OptionId,
Payload: optionalPayloadMap(request.Body.Payload),
})
if err != nil {
return writeSubmitWorkspaceAgentInteractiveError(err), nil
}
generatedSession, err := generatedAgentSession(session)
if err != nil {
return writeSubmitWorkspaceAgentInteractiveError(err), nil
}
if !isRendererEngineCommandOrigin(
request.Params.XTuttiAgentCommandOrigin,
) {
api.recordAgentStimulus(ctx, "interactive.response", string(request.WorkspaceID), string(request.AgentSessionID), map[string]any{
"turnId": request.Body.TurnId,
"requestId": string(request.RequestID),
"action": request.Body.Action,
"optionId": request.Body.OptionId,
"payload": request.Body.Payload,
})
}
return tuttigenerated.SubmitWorkspaceAgentInteractive200JSONResponse{
Session: generatedSession,
}, nil
}
func generatedAgentSessions(sessions []agentservice.Session) ([]tuttigenerated.WorkspaceAgentSession, error) {
result := make([]tuttigenerated.WorkspaceAgentSession, 0, len(sessions))
for _, session := range sessions {
generated, err := generatedAgentSession(session)
if err != nil {
return nil, err
}
result = append(result, generated)
}
return result, nil
}
func generatedAgentSessionPage(page agentservice.SessionPage) (tuttigenerated.WorkspaceAgentSessionPage, error) {
sessions, err := generatedAgentSessions(page.Sessions)
if err != nil {
return tuttigenerated.WorkspaceAgentSessionPage{}, err
}
response := tuttigenerated.WorkspaceAgentSessionPage{
HasMore: page.HasMore,
Sessions: sessions,
TotalCount: page.TotalCount,
}
if strings.TrimSpace(page.NextCursor) != "" {
response.NextCursor = &page.NextCursor
}
return response, nil
}
func generatedAgentSessionSections(sections []agentservice.SessionSection) ([]tuttigenerated.WorkspaceAgentSessionSection, error) {
result := make([]tuttigenerated.WorkspaceAgentSessionSection, 0, len(sections))
for _, section := range sections {
generated, err := generatedAgentSessionSection(section)
if err != nil {
return nil, err
}
result = append(result, generated)
}
return result, nil
}
func generatedAgentSessionSection(section agentservice.SessionSection) (tuttigenerated.WorkspaceAgentSessionSection, error) {
var userProject *tuttigenerated.UserProject
if section.UserProject != nil {
value := generatedUserProject(*section.UserProject)
userProject = &value
}
sessions, err := generatedAgentSessions(section.Sessions)
if err != nil {
return tuttigenerated.WorkspaceAgentSessionSection{}, err
}
response := tuttigenerated.WorkspaceAgentSessionSection{
HasMore: section.HasMore,
Kind: tuttigenerated.WorkspaceAgentSessionSectionKind(section.Kind),
SectionKey: section.SectionKey,
Sessions: sessions,
TotalCount: section.TotalCount,
UserProject: userProject,
}
if strings.TrimSpace(section.NextCursor) != "" {
response.NextCursor = &section.NextCursor
}
return response, nil
}
func composerSettingsFromGenerated(settings tuttigenerated.AgentSessionComposerSettings) agentservice.ComposerSettings {
return agentservice.ComposerSettings{
CodexSaverMode: settings.CodexSaverMode != nil && *settings.CodexSaverMode,
RTKSaverMode: settings.RtkSaverMode != nil && *settings.RtkSaverMode,
Model: optionalStringValue(settings.Model),
PermissionModeID: optionalStringValue(settings.PermissionModeId),
PlanMode: settings.PlanMode != nil && *settings.PlanMode,
BrowserUse: settings.BrowserUse,
ReasoningEffort: optionalStringValue(settings.ReasoningEffort),
Speed: optionalStringValue(settings.Speed),
}
}
func (api DaemonAPI) composerDefaultLocale(ctx context.Context) string {
if api.PreferencesService == nil {
return ""
}
preferences, err := api.PreferencesService.Get(ctx)
if err != nil {
return ""
}
return preferences.Locale
}
func (api DaemonAPI) agentConversationDetailMode(ctx context.Context) string {
if api.PreferencesService == nil {
return preferencesbiz.DefaultDesktopAgentConversationDetailMode
}
preferences, err := api.PreferencesService.Get(ctx)
if err != nil {
return preferencesbiz.DefaultDesktopAgentConversationDetailMode
}
return preferencesbiz.NormalizeDesktopAgentConversationDetailMode(preferences.AgentConversationDetailMode)
}
func composerSettingsPatchFromGenerated(settings tuttigenerated.AgentSessionComposerSettings) agentservice.ComposerSettingsPatch {
return agentservice.ComposerSettingsPatch{
CodexSaverMode: settings.CodexSaverMode,
RTKSaverMode: settings.RtkSaverMode,
Model: settings.Model,
PermissionModeID: settings.PermissionModeId,
PlanMode: settings.PlanMode,
BrowserUse: settings.BrowserUse,
ReasoningEffort: settings.ReasoningEffort,
Speed: settings.Speed,
}
}
func generatedAgentProviderComposerOptions(options agentservice.ComposerOptions) tuttigenerated.AgentProviderComposerOptionsResponse {
effectiveSettings := generatedAgentSessionComposerSettings(options.EffectiveSettings)
return tuttigenerated.AgentProviderComposerOptionsResponse{
CodexSaverModeSupported: &options.CodexSaverModeSupported,
RtkSaverModeSupported: &options.RTKSaverModeSupported,
Behavior: tuttigenerated.AgentProviderComposerBehavior{
CollapseModelOptionsToLatest: options.Behavior.CollapseModelOptionsToLatest,
ModelOptionsAuthoritative: options.Behavior.ModelOptionsAuthoritative,
RefreshModelOptionsAfterSettings: options.Behavior.RefreshModelOptionsAfterSettings,
PrewarmDraftSession: options.Behavior.PrewarmDraftSession,
PlanModeExclusiveWithPermissionMode: options.Behavior.PlanModeExclusiveWithPermissionMode,
},
Capabilities: generatedAgentSessionCapabilities(canonical.NewCapabilitySnapshot(options.Capabilities)),
CapabilityCatalog: generatedAgentProviderCapabilityOptions(options.CapabilityCatalog),
Commands: generatedAgentProviderComposerCommands(options.Commands),
EffectiveSettings: effectiveSettings,
ModelConfig: generatedComposerConfigOption(options.ModelConfig),
PermissionConfig: generatedPermissionConfig(options.PermissionConfig),
Provider: tuttigenerated.WorkspaceAgentProvider(options.Provider),
ReasoningConfig: generatedComposerConfigOption(options.ReasoningConfig),
ReasoningOptionsByModel: generatedAgentProviderComposerReasoningOptionsByModel(
options.ReasoningOptionsByModel,
),
SpeedConfig: generatedComposerConfigOptionPointer(options.SpeedConfig),
RuntimeContext: options.RuntimeContext,
Skills: generatedAgentProviderSkillOptions(options.Skills),
SlashCommandPolicy: generatedAgentSlashCommandPolicy(options.SlashCommandPolicy),
}
}
func generatedAgentSlashCommandPolicy(
policy *providerregistry.SlashCommandPolicyDescriptor,
) *tuttigenerated.AgentSlashCommandPolicy {
if policy == nil {
return nil
}
effects := make([]tuttigenerated.AgentSlashCommandEffectDescriptor, 0, len(policy.CommandEffects))
for _, effect := range policy.CommandEffects {
effects = append(effects, tuttigenerated.AgentSlashCommandEffectDescriptor{
Command: strings.TrimSpace(effect.Command),
Effect: tuttigenerated.AgentSlashCommandEffect(effect.Effect),
})
}
return &tuttigenerated.AgentSlashCommandPolicy{
FallbackCommands: append(make([]string, 0, len(policy.FallbackCommands)), policy.FallbackCommands...),
CommandEffects: effects,
CommandCatalogAuthoritative: boolPointer(policy.CommandCatalogAuthoritative),
}
}
func generatedAgentSessionComposerSettings(settings agentservice.ComposerSettings) tuttigenerated.AgentSessionComposerSettings {
result := tuttigenerated.AgentSessionComposerSettings{
CodexSaverMode: boolPointer(settings.CodexSaverMode),
RtkSaverMode: boolPointer(settings.RTKSaverMode),
Model: optionalStringPointer(strings.TrimSpace(settings.Model)),
PermissionModeId: optionalStringPointer(strings.TrimSpace(settings.PermissionModeID)),
PlanMode: boolPointer(settings.PlanMode),
ReasoningEffort: optionalStringPointer(strings.TrimSpace(settings.ReasoningEffort)),
Speed: optionalStringPointer(strings.TrimSpace(settings.Speed)),
}
if settings.BrowserUse != nil {
result.BrowserUse = settings.BrowserUse
}
return result
}
func generatedPermissionConfig(config agentservice.PermissionConfig) tuttigenerated.PermissionConfig {
result := tuttigenerated.PermissionConfig{
Configurable: config.Configurable,
Modes: make([]tuttigenerated.PermissionModeOption, 0, len(config.Modes)),
}
if strings.TrimSpace(config.DefaultValue) != "" {
result.DefaultValue = optionalStringPointer(config.DefaultValue)
}
for _, mode := range config.Modes {
option := tuttigenerated.PermissionModeOption{
Id: strings.TrimSpace(mode.ID),
Label: strings.TrimSpace(mode.Label),
Semantic: tuttigenerated.PermissionModeSemantic(mode.Semantic),
}
if strings.TrimSpace(mode.Description) != "" {
option.Description = optionalStringPointer(mode.Description)
}
if option.Id != "" && option.Label != "" {
result.Modes = append(result.Modes, option)
}
}
return result
}
func optionalStringValue(input *string) string {
if input == nil {
return ""
}
return strings.TrimSpace(*input)
}
func optionalPayloadMap(input *map[string]interface{}) map[string]any {
if input == nil {
return nil
}
return map[string]any(*input)
}
func optionalStringPointer(value string) *string {
value = strings.TrimSpace(value)
if value == "" {
return nil
}
return &value
}
func generatedAgentGeneratedFiles(files []agentservice.GeneratedFile) []tuttigenerated.WorkspaceAgentGeneratedFileEntry {
result := make([]tuttigenerated.WorkspaceAgentGeneratedFileEntry, 0, len(files))
for _, file := range files {
path := strings.TrimSpace(file.Path)
if path == "" {
continue
}
label := strings.TrimSpace(file.Label)
if label == "" {
label = path
}
result = append(result, tuttigenerated.WorkspaceAgentGeneratedFileEntry{
Label: label,
Path: path,
})
}
return result
}
func generatedAgentSession(session agentservice.Session) (tuttigenerated.WorkspaceAgentSession, error) {
var settings *tuttigenerated.AgentSessionComposerSettings
if session.Settings != nil {
value := generatedAgentSessionComposerSettings(*session.Settings)
settings = &value
}
// Protocol v2 turn state: the session carries an activeTurnId reference
// plus the embedded active turn and pending interactions.
var activeTurn *tuttigenerated.WorkspaceAgentTurn
if session.ActiveTurn != nil {
turn := generatedWorkspaceAgentTurn(*session.ActiveTurn)
activeTurn = &turn
}
var latestTurn *tuttigenerated.WorkspaceAgentTurn
if session.LatestTurn != nil {
turn := generatedWorkspaceAgentTurn(*session.LatestTurn)
latestTurn = &turn
}
pendingInteractions := make([]tuttigenerated.WorkspaceAgentInteraction, 0, len(session.PendingInteractions))
for _, interaction := range session.PendingInteractions {
pendingInteractions = append(pendingInteractions, generatedWorkspaceAgentInteraction(interaction))
}
latestTurnInteractions := make([]tuttigenerated.WorkspaceAgentInteraction, 0, len(session.LatestTurnInteractions))
for _, interaction := range session.LatestTurnInteractions {
latestTurnInteractions = append(latestTurnInteractions, generatedWorkspaceAgentInteraction(interaction))
}
updatedAtUnixMS := session.CreatedAt.UnixMilli()
if session.UpdatedAt != nil {
updatedAtUnixMS = session.UpdatedAt.UnixMilli()
}
var endedAtUnixMS *int64
if session.EndedAt != nil {
value := session.EndedAt.UnixMilli()
endedAtUnixMS = &value
}
generatedSettings := tuttigenerated.AgentSessionComposerSettings{}
if settings != nil {
generatedSettings = *settings
}
tuttiModeActivation, err := generatedTuttiModeActivation(session.TuttiModeActivation)
if err != nil {
return tuttigenerated.WorkspaceAgentSession{}, err
}
messageVersion, err := generatedWorkspaceAgentSafeInteger("session message version", session.MessageVersion)
if err != nil {
return tuttigenerated.WorkspaceAgentSession{}, fmt.Errorf("project workspace agent session %q: %w", session.ID, err)
}
var forkedFrom *tuttigenerated.WorkspaceAgentSessionForkLineage
if session.ForkedFrom != nil {
forkedFrom = &tuttigenerated.WorkspaceAgentSessionForkLineage{
ForkedAtUnixMs: session.ForkedFrom.ForkedAtUnixMS,
OperationId: strings.TrimSpace(session.ForkedFrom.OperationID),
SourceAgentSessionId: strings.TrimSpace(session.ForkedFrom.SourceAgentSessionID),
SourceTurnId: strings.TrimSpace(session.ForkedFrom.SourceTurnID),
TargetTurnId: strings.TrimSpace(session.ForkedFrom.TargetTurnID),
}
}
goalSyncState := generatedAgentSessionGoalSyncState(session.GoalSyncState)
return tuttigenerated.WorkspaceAgentSession{
ActiveTurn: activeTurn,
ActiveTurnId: optionalStringPointer(strings.TrimSpace(session.ActiveTurnID)),
AgentTargetId: optionalStringPointer(strings.TrimSpace(session.AgentTargetID)),
Capabilities: generatedAgentSessionCapabilities(session.Capabilities),
CreatedAtUnixMs: session.CreatedAt.UnixMilli(),
Cwd: stringPointer(strings.TrimSpace(session.Cwd)),
EndedAtUnixMs: endedAtUnixMS,
ForkedFrom: forkedFrom,
Goal: generatedAgentSessionGoal(session.Metadata.Goal),
GoalSyncState: goalSyncState,
Id: session.ID,
Imported: session.Metadata.Imported,
Isolation: generatedAgentSessionIsolation(session.Isolation),
Kind: tuttigenerated.WorkspaceAgentSessionKind(session.Kind),
LatestTurn: latestTurn,
LatestTurnInteractions: latestTurnInteractions,
MessageVersion: messageVersion,
LifecycleCapabilities: tuttigenerated.WorkspaceAgentSessionLifecycleCapabilities{
Fork: session.LifecycleCapabilities.Fork,
ForkThroughTurn: session.LifecycleCapabilities.ForkThroughTurn,
},
ParentAgentSessionId: optionalStringPointer(strings.TrimSpace(session.ParentAgentSessionID)),
ParentToolCallId: optionalStringPointer(strings.TrimSpace(session.ParentToolCallID)),
ParentTurnId: optionalStringPointer(strings.TrimSpace(session.ParentTurnID)),
PendingInteractions: pendingInteractions,
PermissionConfig: generatedPermissionConfig(session.PermissionConfig),
Provider: tuttigenerated.WorkspaceAgentProvider(session.Provider),
ProviderSessionId: stringPointer(strings.TrimSpace(session.ProviderSessionID)),
PinnedAtUnixMs: int64Pointer(session.PinnedAtUnixMS),
RailSectionKey: strings.TrimSpace(session.RailSectionKey),
Resumable: session.Resumable,
RootAgentSessionId: optionalStringPointer(strings.TrimSpace(session.RootAgentSessionID)),
RootTurnId: optionalStringPointer(strings.TrimSpace(session.RootTurnID)),
Settings: generatedSettings,
Title: session.Title,
TuttiModeActivation: tuttiModeActivation,
UpdatedAtUnixMs: updatedAtUnixMS,
Usage: generatedAgentSessionUsage(session.Metadata.Usage),
Visible: session.Visible,
}, nil
}
func int64Pointer(value int64) *int64 {
if value == 0 {
return nil
}
return &value
}