Files
tutti/services/tuttid/api/daemon_agent_session_fork.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

304 lines
11 KiB
Go

package api
import (
"context"
"errors"
"fmt"
"strings"
tuttigenerated "github.com/tutti-os/tutti/services/tuttid/api/generated"
"github.com/tutti-os/tutti/services/tuttid/apierrors"
agentservice "github.com/tutti-os/tutti/services/tuttid/service/agent"
)
func (api DaemonAPI) ForkWorkspaceAgentSession(
ctx context.Context,
request tuttigenerated.ForkWorkspaceAgentSessionRequestObject,
) (tuttigenerated.ForkWorkspaceAgentSessionResponseObject, error) {
if api.AgentSessionService == nil {
return tuttigenerated.ForkWorkspaceAgentSession503JSONResponse{
ServiceUnavailableErrorJSONResponse: agentSessionServiceUnavailableError(),
}, nil
}
if request.Body == nil {
return writeForkWorkspaceAgentSessionError(agentservice.ErrInvalidArgument), nil
}
point, err := request.Body.Point.AsWorkspaceAgentSessionForkThroughTurnPoint()
if err != nil || point.Type != tuttigenerated.ThroughTurn ||
strings.TrimSpace(point.TurnId) == "" ||
strings.TrimSpace(request.Body.RequestId) == "" {
return writeForkWorkspaceAgentSessionError(agentservice.ErrInvalidArgument), nil
}
operation, err := api.AgentSessionService.Fork(
ctx,
string(request.WorkspaceID),
string(request.AgentSessionID),
agentservice.ForkSessionInput{
TargetAgentSessionID: request.Body.TargetAgentSessionId.String(),
RequestID: request.Body.RequestId,
ThroughTurnID: point.TurnId,
},
)
if err != nil {
return writeForkWorkspaceAgentSessionError(err), nil
}
generatedOperation, err := generatedAgentSessionForkOperation(operation)
if err != nil {
return writeForkWorkspaceAgentSessionError(err), nil
}
response := tuttigenerated.WorkspaceAgentSessionForkOperationResponse{
Operation: generatedOperation,
}
if operation.Status == agentservice.SessionForkOperationAccepted {
return tuttigenerated.ForkWorkspaceAgentSession202JSONResponse(response), nil
}
return tuttigenerated.ForkWorkspaceAgentSession200JSONResponse(response), nil
}
func (api DaemonAPI) GetWorkspaceAgentSessionForkOperation(
ctx context.Context,
request tuttigenerated.GetWorkspaceAgentSessionForkOperationRequestObject,
) (tuttigenerated.GetWorkspaceAgentSessionForkOperationResponseObject, error) {
if api.AgentSessionService == nil {
return tuttigenerated.GetWorkspaceAgentSessionForkOperation503JSONResponse{
ServiceUnavailableErrorJSONResponse: agentSessionServiceUnavailableError(),
}, nil
}
operation, err := api.AgentSessionService.GetSessionForkOperation(
ctx,
string(request.WorkspaceID),
string(request.OperationID),
)
if err != nil {
return writeGetWorkspaceAgentSessionForkOperationError(err), nil
}
generatedOperation, err := generatedAgentSessionForkOperation(operation)
if err != nil {
return writeGetWorkspaceAgentSessionForkOperationError(err), nil
}
return tuttigenerated.GetWorkspaceAgentSessionForkOperation200JSONResponse{
Operation: generatedOperation,
}, nil
}
func (api DaemonAPI) AcknowledgeWorkspaceAgentSessionForkOperation(
ctx context.Context,
request tuttigenerated.AcknowledgeWorkspaceAgentSessionForkOperationRequestObject,
) (tuttigenerated.AcknowledgeWorkspaceAgentSessionForkOperationResponseObject, error) {
if api.AgentSessionService == nil {
return tuttigenerated.AcknowledgeWorkspaceAgentSessionForkOperation503JSONResponse{
ServiceUnavailableErrorJSONResponse: agentSessionServiceUnavailableError(),
}, nil
}
operation, err := api.AgentSessionService.AcknowledgeSessionForkOperation(
ctx,
string(request.WorkspaceID),
string(request.OperationID),
)
if err != nil {
return writeAcknowledgeWorkspaceAgentSessionForkOperationError(err), nil
}
generatedOperation, err := generatedAgentSessionForkOperation(operation)
if err != nil {
return writeAcknowledgeWorkspaceAgentSessionForkOperationError(err), nil
}
return tuttigenerated.AcknowledgeWorkspaceAgentSessionForkOperation200JSONResponse{
Operation: generatedOperation,
}, nil
}
func generatedAgentSessionForkOperation(
operation agentservice.SessionForkOperation,
) (tuttigenerated.WorkspaceAgentSessionForkOperation, error) {
var point tuttigenerated.WorkspaceAgentSessionForkPoint
if operation.Point.Type != "throughTurn" ||
strings.TrimSpace(operation.Point.TurnID) == "" {
return tuttigenerated.WorkspaceAgentSessionForkOperation{},
fmt.Errorf("unsupported agent session fork point %q", operation.Point.Type)
}
if err := point.FromWorkspaceAgentSessionForkThroughTurnPoint(
tuttigenerated.WorkspaceAgentSessionForkThroughTurnPoint{
Type: tuttigenerated.ThroughTurn,
TurnId: strings.TrimSpace(operation.Point.TurnID),
},
); err != nil {
return tuttigenerated.WorkspaceAgentSessionForkOperation{}, err
}
var session *tuttigenerated.WorkspaceAgentSession
if operation.Session != nil {
value, err := generatedAgentSession(*operation.Session)
if err != nil {
return tuttigenerated.WorkspaceAgentSessionForkOperation{}, err
}
session = &value
}
var lineage *tuttigenerated.WorkspaceAgentSessionForkLineage
if operation.Lineage != nil {
lineage = &tuttigenerated.WorkspaceAgentSessionForkLineage{
ForkedAtUnixMs: operation.Lineage.ForkedAtUnixMS,
OperationId: strings.TrimSpace(operation.Lineage.OperationID),
SourceAgentSessionId: strings.TrimSpace(operation.Lineage.SourceAgentSessionID),
SourceTurnId: strings.TrimSpace(operation.Lineage.SourceTurnID),
TargetTurnId: strings.TrimSpace(operation.Lineage.TargetTurnID),
}
}
var operationError *string
if operation.Error != nil {
value := strings.TrimSpace(*operation.Error)
operationError = &value
}
return tuttigenerated.WorkspaceAgentSessionForkOperation{
Error: operationError,
Lineage: lineage,
OperationId: strings.TrimSpace(operation.OperationID),
Point: point,
Phase: tuttigenerated.WorkspaceAgentSessionForkOperationPhase(
operation.Phase,
),
RequestId: strings.TrimSpace(operation.RequestID),
Session: session,
SourceAgentSessionId: strings.TrimSpace(operation.SourceAgentSessionID),
Status: tuttigenerated.WorkspaceAgentSessionForkOperationStatus(
operation.Status,
),
TargetAgentSessionId: strings.TrimSpace(operation.TargetAgentSessionID),
}, nil
}
func writeGetWorkspaceAgentSessionForkOperationError(
err error,
) tuttigenerated.GetWorkspaceAgentSessionForkOperationResponseObject {
protocolErr := apierrors.Classify(err)
switch {
case errors.Is(err, agentservice.ErrSessionForkOperationNotFound):
protocolErr = apierrors.New(
404,
tuttigenerated.AgentSessionForkOperationNotFound,
"agent_session_fork_operation_not_found",
apierrors.WithCause(err),
)
return tuttigenerated.GetWorkspaceAgentSessionForkOperation404JSONResponse(
protocolErrorResponse(protocolErr),
)
case protocolErr.Code == tuttigenerated.InvalidRequest:
return tuttigenerated.GetWorkspaceAgentSessionForkOperation400JSONResponse{
InvalidRequestErrorJSONResponse: invalidRequestError(protocolErr),
}
default:
return tuttigenerated.GetWorkspaceAgentSessionForkOperation502JSONResponse{
WorkspaceOperationErrorJSONResponse: workspaceOperationError(protocolErr),
}
}
}
func writeAcknowledgeWorkspaceAgentSessionForkOperationError(
err error,
) tuttigenerated.AcknowledgeWorkspaceAgentSessionForkOperationResponseObject {
protocolErr := apierrors.Classify(err)
switch {
case errors.Is(err, agentservice.ErrSessionForkOperationNotFound):
protocolErr = apierrors.New(
404,
tuttigenerated.AgentSessionForkOperationNotFound,
"agent_session_fork_operation_not_found",
apierrors.WithCause(err),
)
return tuttigenerated.AcknowledgeWorkspaceAgentSessionForkOperation404JSONResponse(
protocolErrorResponse(protocolErr),
)
case errors.Is(err, agentservice.ErrSessionForkConflict):
protocolErr = apierrors.New(
409,
tuttigenerated.WorkspaceOperationFailed,
"agent_session_fork_acknowledge_conflict",
apierrors.WithCause(err),
)
return tuttigenerated.AcknowledgeWorkspaceAgentSessionForkOperation409JSONResponse(
protocolErrorResponse(protocolErr),
)
case protocolErr.Code == tuttigenerated.InvalidRequest:
return tuttigenerated.AcknowledgeWorkspaceAgentSessionForkOperation400JSONResponse{
InvalidRequestErrorJSONResponse: invalidRequestError(protocolErr),
}
default:
return tuttigenerated.AcknowledgeWorkspaceAgentSessionForkOperation502JSONResponse{
WorkspaceOperationErrorJSONResponse: workspaceOperationError(protocolErr),
}
}
}
func writeForkWorkspaceAgentSessionError(
err error,
) tuttigenerated.ForkWorkspaceAgentSessionResponseObject {
protocolErr := apierrors.Classify(err)
switch {
case errors.Is(err, agentservice.ErrSessionForkUnsupported):
protocolErr = apierrors.New(
409,
tuttigenerated.WorkspaceOperationFailed,
"agent_session_fork_unsupported",
apierrors.WithCause(err),
)
return tuttigenerated.ForkWorkspaceAgentSession409JSONResponse(
protocolErrorResponse(protocolErr),
)
case errors.Is(err, agentservice.ErrSessionForkConflict):
options := []apierrors.Option{apierrors.WithCause(err)}
if boundaryReason := sessionForkBoundaryReason(err); boundaryReason != "" {
options = append(options, apierrors.WithParams(map[string]any{
"forkBoundaryReason": boundaryReason,
}))
}
protocolErr = apierrors.New(
409,
tuttigenerated.WorkspaceOperationFailed,
"agent_session_fork_conflict",
options...,
)
return tuttigenerated.ForkWorkspaceAgentSession409JSONResponse(
protocolErrorResponse(protocolErr),
)
case errors.Is(err, agentservice.ErrSessionForkInProgress):
protocolErr = apierrors.New(
409,
tuttigenerated.WorkspaceOperationFailed,
"agent_session_fork_in_progress",
apierrors.WithCause(err),
)
protocolErr.Retryable = true
return tuttigenerated.ForkWorkspaceAgentSession409JSONResponse(
protocolErrorResponse(protocolErr),
)
case protocolErr.Code == tuttigenerated.InvalidRequest:
return tuttigenerated.ForkWorkspaceAgentSession400JSONResponse{
InvalidRequestErrorJSONResponse: invalidRequestError(protocolErr),
}
case protocolErr.Code == tuttigenerated.WorkspaceNotFound:
return tuttigenerated.ForkWorkspaceAgentSession404JSONResponse{
WorkspaceNotFoundErrorJSONResponse: workspaceNotFoundError(protocolErr),
}
default:
if errors.Is(err, agentservice.ErrSessionForkDeliveryUnknown) {
protocolErr.Retryable = true
protocolErr.Reason = "agent_session_fork_delivery_unknown"
} else if errors.Is(err, agentservice.ErrSessionForkFailed) {
protocolErr.Reason = "agent_session_fork_failed"
}
return tuttigenerated.ForkWorkspaceAgentSession502JSONResponse{
WorkspaceOperationErrorJSONResponse: workspaceOperationError(protocolErr),
}
}
}
type sessionForkBoundaryReasoner interface {
ForkBoundaryReason() string
}
func sessionForkBoundaryReason(err error) string {
var reasoner sessionForkBoundaryReasoner
if !errors.As(err, &reasoner) {
return ""
}
return strings.TrimSpace(reasoner.ForkBoundaryReason())
}