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
304 lines
11 KiB
Go
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())
|
|
}
|