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
357 lines
15 KiB
Go
357 lines
15 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
agenthost "github.com/tutti-os/tutti/packages/agent/host"
|
|
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"
|
|
)
|
|
|
|
type SideConversationService interface {
|
|
ResolveSideConversation(context.Context, string, string) (agenthost.SideConversationCapabilities, error)
|
|
OpenSideConversation(context.Context, string, string, agentservice.OpenSideConversationInput) (agentservice.SideConversation, error)
|
|
SendSideConversation(context.Context, string, string, agentservice.SendSideConversationInput) (agenthost.RuntimeExecResult, error)
|
|
CancelSideConversation(context.Context, string, string, string) (agenthost.RuntimeCancelResult, error)
|
|
SubmitSideConversationInteractive(context.Context, agenthost.RuntimeSubmitInteractiveInput) (agenthost.RuntimeSubmitInteractiveResult, error)
|
|
CloseSideConversation(context.Context, string, string) error
|
|
}
|
|
|
|
func generatedSideCapabilities(
|
|
input agenthost.SideConversationCapabilities,
|
|
) tuttigenerated.WorkspaceAgentSideCapabilities {
|
|
return tuttigenerated.WorkspaceAgentSideCapabilities{
|
|
Supported: input.Supported, ActiveSourceTurn: input.ActiveSourceTurn,
|
|
Ephemeral: input.Ephemeral, HideInheritedTurns: input.HideInheritedTurns,
|
|
ModelBoundaryInjected: input.ModelBoundaryInjected,
|
|
}
|
|
}
|
|
|
|
func generatedSideConversation(
|
|
input agentservice.SideConversation,
|
|
) tuttigenerated.WorkspaceAgentSideConversation {
|
|
status := input.Status
|
|
if status == "" {
|
|
status = "idle"
|
|
}
|
|
return tuttigenerated.WorkspaceAgentSideConversation{
|
|
WorkspaceId: input.WorkspaceID, SourceAgentSessionId: input.SourceAgentSessionID,
|
|
SideAgentSessionId: input.SideAgentSessionID, Provider: input.Provider,
|
|
Status: status, Capabilities: generatedSideCapabilities(input.Capabilities),
|
|
}
|
|
}
|
|
|
|
func sideConflictError(err error) tuttigenerated.ApiErrorResponse {
|
|
reason := "agent_side_conversation_conflict"
|
|
switch {
|
|
case errors.Is(err, agentservice.ErrSideConversationUnsupported):
|
|
reason = "agent_side_conversation_unsupported"
|
|
case errors.Is(err, agentservice.ErrSideConversationInProgress):
|
|
reason = "agent_side_conversation_in_progress"
|
|
case errors.Is(err, agentservice.ErrSideConversationExpired):
|
|
reason = "agent_side_conversation_expired"
|
|
case errors.Is(err, agentservice.ErrRuntimeSessionDisconnected):
|
|
reason = "agent_side_conversation_expired"
|
|
case errors.Is(err, agenthost.ErrInteractiveRequestNotLive):
|
|
reason = "agent_side_interaction_not_live"
|
|
case errors.Is(err, agenthost.ErrInteractiveAlreadyAnswered):
|
|
reason = "agent_side_interaction_already_answered"
|
|
}
|
|
return protocolErrorResponse(apierrors.New(
|
|
apierrors.StatusWorkspaceIssueExists,
|
|
tuttigenerated.WorkspaceOperationFailed,
|
|
reason,
|
|
apierrors.WithCause(err),
|
|
))
|
|
}
|
|
|
|
func isSideConflict(err error) bool {
|
|
return errors.Is(err, agentservice.ErrSideConversationUnsupported) ||
|
|
errors.Is(err, agentservice.ErrSideConversationInProgress) ||
|
|
errors.Is(err, agentservice.ErrSideConversationConflict) ||
|
|
errors.Is(err, agentservice.ErrSideConversationExpired) ||
|
|
errors.Is(err, agentservice.ErrRuntimeSessionDisconnected) ||
|
|
errors.Is(err, agenthost.ErrInteractiveRequestNotLive) ||
|
|
errors.Is(err, agenthost.ErrInteractiveAlreadyAnswered)
|
|
}
|
|
|
|
func (api DaemonAPI) ResolveWorkspaceAgentSideCapabilities(
|
|
ctx context.Context,
|
|
request tuttigenerated.ResolveWorkspaceAgentSideCapabilitiesRequestObject,
|
|
) (tuttigenerated.ResolveWorkspaceAgentSideCapabilitiesResponseObject, error) {
|
|
if api.SideConversationService == nil {
|
|
return tuttigenerated.ResolveWorkspaceAgentSideCapabilities503JSONResponse{
|
|
ServiceUnavailableErrorJSONResponse: agentSessionServiceUnavailableError(),
|
|
}, nil
|
|
}
|
|
if !api.agentSideConversationEnabled(ctx) {
|
|
return tuttigenerated.ResolveWorkspaceAgentSideCapabilities200JSONResponse{
|
|
Capabilities: generatedSideCapabilities(agenthost.SideConversationCapabilities{}),
|
|
}, nil
|
|
}
|
|
capabilities, err := api.SideConversationService.ResolveSideConversation(
|
|
ctx, string(request.WorkspaceID), string(request.AgentSessionID),
|
|
)
|
|
if err != nil {
|
|
if isSideConflict(err) {
|
|
return tuttigenerated.ResolveWorkspaceAgentSideCapabilities409JSONResponse(
|
|
sideConflictError(err),
|
|
), nil
|
|
}
|
|
protocolErr := apierrors.Classify(err)
|
|
if protocolErr.Code == tuttigenerated.InvalidRequest {
|
|
return tuttigenerated.ResolveWorkspaceAgentSideCapabilities400JSONResponse{
|
|
InvalidRequestErrorJSONResponse: invalidRequestError(protocolErr),
|
|
}, nil
|
|
}
|
|
if protocolErr.Code == tuttigenerated.WorkspaceNotFound {
|
|
return tuttigenerated.ResolveWorkspaceAgentSideCapabilities404JSONResponse{
|
|
WorkspaceNotFoundErrorJSONResponse: workspaceNotFoundError(protocolErr),
|
|
}, nil
|
|
}
|
|
return tuttigenerated.ResolveWorkspaceAgentSideCapabilities502JSONResponse{
|
|
WorkspaceOperationErrorJSONResponse: workspaceOperationError(protocolErr),
|
|
}, nil
|
|
}
|
|
return tuttigenerated.ResolveWorkspaceAgentSideCapabilities200JSONResponse{
|
|
Capabilities: generatedSideCapabilities(capabilities),
|
|
}, nil
|
|
}
|
|
|
|
func (api DaemonAPI) OpenWorkspaceAgentSideConversation(
|
|
ctx context.Context,
|
|
request tuttigenerated.OpenWorkspaceAgentSideConversationRequestObject,
|
|
) (tuttigenerated.OpenWorkspaceAgentSideConversationResponseObject, error) {
|
|
if api.SideConversationService == nil {
|
|
return tuttigenerated.OpenWorkspaceAgentSideConversation503JSONResponse{
|
|
ServiceUnavailableErrorJSONResponse: agentSessionServiceUnavailableError(),
|
|
}, nil
|
|
}
|
|
if !api.agentSideConversationEnabled(ctx) {
|
|
return tuttigenerated.OpenWorkspaceAgentSideConversation400JSONResponse{
|
|
InvalidRequestErrorJSONResponse: agentSideConversationDisabledError(),
|
|
}, nil
|
|
}
|
|
if request.Body == nil {
|
|
return tuttigenerated.OpenWorkspaceAgentSideConversation400JSONResponse{
|
|
InvalidRequestErrorJSONResponse: invalidRequestError(
|
|
apierrors.EmptyBody(apierrors.WithDeveloperMessage("empty body")),
|
|
),
|
|
}, nil
|
|
}
|
|
side, err := api.SideConversationService.OpenSideConversation(
|
|
ctx, string(request.WorkspaceID), string(request.AgentSessionID),
|
|
agentservice.OpenSideConversationInput{
|
|
SideAgentSessionID: request.Body.SideAgentSessionId,
|
|
RequestID: request.Body.RequestId,
|
|
},
|
|
)
|
|
if err != nil {
|
|
if isSideConflict(err) {
|
|
return tuttigenerated.OpenWorkspaceAgentSideConversation409JSONResponse(
|
|
sideConflictError(err),
|
|
), nil
|
|
}
|
|
protocolErr := apierrors.Classify(err)
|
|
if protocolErr.Code == tuttigenerated.InvalidRequest {
|
|
return tuttigenerated.OpenWorkspaceAgentSideConversation400JSONResponse{
|
|
InvalidRequestErrorJSONResponse: invalidRequestError(protocolErr),
|
|
}, nil
|
|
}
|
|
if protocolErr.Code == tuttigenerated.WorkspaceNotFound {
|
|
return tuttigenerated.OpenWorkspaceAgentSideConversation404JSONResponse{
|
|
WorkspaceNotFoundErrorJSONResponse: workspaceNotFoundError(protocolErr),
|
|
}, nil
|
|
}
|
|
return tuttigenerated.OpenWorkspaceAgentSideConversation502JSONResponse{
|
|
WorkspaceOperationErrorJSONResponse: workspaceOperationError(protocolErr),
|
|
}, nil
|
|
}
|
|
return tuttigenerated.OpenWorkspaceAgentSideConversation200JSONResponse{
|
|
Side: generatedSideConversation(side),
|
|
}, nil
|
|
}
|
|
|
|
func (api DaemonAPI) SendWorkspaceAgentSideConversationInput(
|
|
ctx context.Context,
|
|
request tuttigenerated.SendWorkspaceAgentSideConversationInputRequestObject,
|
|
) (tuttigenerated.SendWorkspaceAgentSideConversationInputResponseObject, error) {
|
|
if api.SideConversationService == nil {
|
|
return tuttigenerated.SendWorkspaceAgentSideConversationInput503JSONResponse{
|
|
ServiceUnavailableErrorJSONResponse: agentSessionServiceUnavailableError(),
|
|
}, nil
|
|
}
|
|
if request.Body == nil {
|
|
return tuttigenerated.SendWorkspaceAgentSideConversationInput400JSONResponse{
|
|
InvalidRequestErrorJSONResponse: invalidRequestError(
|
|
apierrors.EmptyBody(apierrors.WithDeveloperMessage("empty body")),
|
|
),
|
|
}, nil
|
|
}
|
|
result, err := api.SideConversationService.SendSideConversation(
|
|
ctx, string(request.WorkspaceID), request.SideAgentSessionID,
|
|
agentservice.SendSideConversationInput{
|
|
TurnID: request.Body.TurnId, ClientSubmitID: request.Body.ClientSubmitId,
|
|
Content: agentPromptContentFromGenerated(request.Body.Content),
|
|
DisplayPrompt: stringPtrValue(request.Body.DisplayPrompt),
|
|
},
|
|
)
|
|
if err != nil {
|
|
if isSideConflict(err) {
|
|
return tuttigenerated.SendWorkspaceAgentSideConversationInput409JSONResponse(
|
|
sideConflictError(err),
|
|
), nil
|
|
}
|
|
protocolErr := apierrors.Classify(err)
|
|
if protocolErr.Code == tuttigenerated.InvalidRequest {
|
|
return tuttigenerated.SendWorkspaceAgentSideConversationInput400JSONResponse{
|
|
InvalidRequestErrorJSONResponse: invalidRequestError(protocolErr),
|
|
}, nil
|
|
}
|
|
if protocolErr.Code == tuttigenerated.WorkspaceNotFound {
|
|
return tuttigenerated.SendWorkspaceAgentSideConversationInput404JSONResponse{
|
|
WorkspaceNotFoundErrorJSONResponse: workspaceNotFoundError(protocolErr),
|
|
}, nil
|
|
}
|
|
return tuttigenerated.SendWorkspaceAgentSideConversationInput502JSONResponse{
|
|
WorkspaceOperationErrorJSONResponse: workspaceOperationError(protocolErr),
|
|
}, nil
|
|
}
|
|
return tuttigenerated.SendWorkspaceAgentSideConversationInput200JSONResponse{
|
|
SideAgentSessionId: result.AgentSessionID, TurnId: result.TurnID,
|
|
Accepted: result.Accepted, Status: result.Status,
|
|
}, nil
|
|
}
|
|
|
|
func (api DaemonAPI) CancelWorkspaceAgentSideConversationTurn(
|
|
ctx context.Context,
|
|
request tuttigenerated.CancelWorkspaceAgentSideConversationTurnRequestObject,
|
|
) (tuttigenerated.CancelWorkspaceAgentSideConversationTurnResponseObject, error) {
|
|
if api.SideConversationService == nil {
|
|
return tuttigenerated.CancelWorkspaceAgentSideConversationTurn503JSONResponse{
|
|
ServiceUnavailableErrorJSONResponse: agentSessionServiceUnavailableError(),
|
|
}, nil
|
|
}
|
|
result, err := api.SideConversationService.CancelSideConversation(
|
|
ctx, string(request.WorkspaceID), request.SideAgentSessionID, string(request.TurnID),
|
|
)
|
|
if err != nil {
|
|
if isSideConflict(err) {
|
|
return tuttigenerated.CancelWorkspaceAgentSideConversationTurn409JSONResponse(
|
|
sideConflictError(err),
|
|
), nil
|
|
}
|
|
protocolErr := apierrors.Classify(err)
|
|
if protocolErr.Code == tuttigenerated.InvalidRequest {
|
|
return tuttigenerated.CancelWorkspaceAgentSideConversationTurn400JSONResponse{
|
|
InvalidRequestErrorJSONResponse: invalidRequestError(protocolErr),
|
|
}, nil
|
|
}
|
|
if protocolErr.Code == tuttigenerated.WorkspaceNotFound {
|
|
return tuttigenerated.CancelWorkspaceAgentSideConversationTurn404JSONResponse{
|
|
WorkspaceNotFoundErrorJSONResponse: workspaceNotFoundError(protocolErr),
|
|
}, nil
|
|
}
|
|
return tuttigenerated.CancelWorkspaceAgentSideConversationTurn502JSONResponse{
|
|
WorkspaceOperationErrorJSONResponse: workspaceOperationError(protocolErr),
|
|
}, nil
|
|
}
|
|
return tuttigenerated.CancelWorkspaceAgentSideConversationTurn200JSONResponse{
|
|
Canceled: result.Canceled, TargetAbsent: result.TargetAbsent,
|
|
}, nil
|
|
}
|
|
|
|
func (api DaemonAPI) SubmitWorkspaceAgentSideConversationInteractive(
|
|
ctx context.Context,
|
|
request tuttigenerated.SubmitWorkspaceAgentSideConversationInteractiveRequestObject,
|
|
) (tuttigenerated.SubmitWorkspaceAgentSideConversationInteractiveResponseObject, error) {
|
|
if api.SideConversationService == nil {
|
|
return tuttigenerated.SubmitWorkspaceAgentSideConversationInteractive503JSONResponse{
|
|
ServiceUnavailableErrorJSONResponse: agentSessionServiceUnavailableError(),
|
|
}, nil
|
|
}
|
|
if request.Body == nil {
|
|
return tuttigenerated.SubmitWorkspaceAgentSideConversationInteractive400JSONResponse{
|
|
InvalidRequestErrorJSONResponse: invalidRequestError(
|
|
apierrors.EmptyBody(apierrors.WithDeveloperMessage("empty body")),
|
|
),
|
|
}, nil
|
|
}
|
|
result, err := api.SideConversationService.SubmitSideConversationInteractive(
|
|
ctx,
|
|
agenthost.RuntimeSubmitInteractiveInput{
|
|
WorkspaceID: string(request.WorkspaceID), RootAgentSessionID: request.SideAgentSessionID,
|
|
AgentSessionID: request.SideAgentSessionID, TurnID: string(request.TurnID),
|
|
RequestID: string(request.RequestID), Action: stringPtrValue(request.Body.Action),
|
|
OptionID: stringPtrValue(request.Body.OptionId), Payload: optionalPayloadMap(request.Body.Payload),
|
|
},
|
|
)
|
|
if err != nil {
|
|
if isSideConflict(err) {
|
|
return tuttigenerated.SubmitWorkspaceAgentSideConversationInteractive409JSONResponse(
|
|
sideConflictError(err),
|
|
), nil
|
|
}
|
|
if errors.Is(err, agenthost.ErrInteractiveResponseInvalid) {
|
|
return tuttigenerated.SubmitWorkspaceAgentSideConversationInteractive400JSONResponse{
|
|
InvalidRequestErrorJSONResponse: invalidRequestError(
|
|
apierrors.InvalidRequest(
|
|
"agent_side_interaction_invalid_response",
|
|
apierrors.WithCause(err),
|
|
),
|
|
),
|
|
}, nil
|
|
}
|
|
protocolErr := apierrors.Classify(err)
|
|
if protocolErr.Code == tuttigenerated.InvalidRequest {
|
|
return tuttigenerated.SubmitWorkspaceAgentSideConversationInteractive400JSONResponse{
|
|
InvalidRequestErrorJSONResponse: invalidRequestError(protocolErr),
|
|
}, nil
|
|
}
|
|
if protocolErr.Code == tuttigenerated.WorkspaceNotFound {
|
|
return tuttigenerated.SubmitWorkspaceAgentSideConversationInteractive404JSONResponse{
|
|
WorkspaceNotFoundErrorJSONResponse: workspaceNotFoundError(protocolErr),
|
|
}, nil
|
|
}
|
|
return tuttigenerated.SubmitWorkspaceAgentSideConversationInteractive502JSONResponse{
|
|
WorkspaceOperationErrorJSONResponse: workspaceOperationError(protocolErr),
|
|
}, nil
|
|
}
|
|
return tuttigenerated.SubmitWorkspaceAgentSideConversationInteractive200JSONResponse{
|
|
Disposition: string(result.Disposition),
|
|
}, nil
|
|
}
|
|
|
|
func (api DaemonAPI) CloseWorkspaceAgentSideConversation(
|
|
ctx context.Context,
|
|
request tuttigenerated.CloseWorkspaceAgentSideConversationRequestObject,
|
|
) (tuttigenerated.CloseWorkspaceAgentSideConversationResponseObject, error) {
|
|
if api.SideConversationService == nil {
|
|
return tuttigenerated.CloseWorkspaceAgentSideConversation503JSONResponse{
|
|
ServiceUnavailableErrorJSONResponse: agentSessionServiceUnavailableError(),
|
|
}, nil
|
|
}
|
|
err := api.SideConversationService.CloseSideConversation(
|
|
ctx, string(request.WorkspaceID), request.SideAgentSessionID,
|
|
)
|
|
if err != nil && !errors.Is(err, agentservice.ErrSideConversationExpired) {
|
|
protocolErr := apierrors.Classify(err)
|
|
if protocolErr.Code == tuttigenerated.InvalidRequest {
|
|
return tuttigenerated.CloseWorkspaceAgentSideConversation400JSONResponse{
|
|
InvalidRequestErrorJSONResponse: invalidRequestError(protocolErr),
|
|
}, nil
|
|
}
|
|
if protocolErr.Code == tuttigenerated.WorkspaceNotFound {
|
|
return tuttigenerated.CloseWorkspaceAgentSideConversation404JSONResponse{
|
|
WorkspaceNotFoundErrorJSONResponse: workspaceNotFoundError(protocolErr),
|
|
}, nil
|
|
}
|
|
return tuttigenerated.CloseWorkspaceAgentSideConversation502JSONResponse{
|
|
WorkspaceOperationErrorJSONResponse: workspaceOperationError(protocolErr),
|
|
}, nil
|
|
}
|
|
return tuttigenerated.CloseWorkspaceAgentSideConversation204Response{}, nil
|
|
}
|