diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index c60acbdf30..b7bb7cc3e1 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -3643,6 +3643,65 @@ ], "type": "object" }, + "ThreadQueueAddParams": { + "properties": { + "threadId": { + "type": "string" + }, + "turnStartParams": { + "$ref": "#/definitions/TurnStartParams" + } + }, + "required": [ + "threadId", + "turnStartParams" + ], + "type": "object" + }, + "ThreadQueueDeleteParams": { + "properties": { + "queuedTurnId": { + "type": "string" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "queuedTurnId", + "threadId" + ], + "type": "object" + }, + "ThreadQueueListParams": { + "properties": { + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "type": "object" + }, + "ThreadQueueReorderParams": { + "properties": { + "queuedTurnIds": { + "items": { + "type": "string" + }, + "type": "array" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "queuedTurnIds", + "threadId" + ], + "type": "object" + }, "ThreadReadParams": { "properties": { "includeTurns": { @@ -4572,6 +4631,102 @@ "title": "Thread/name/setRequest", "type": "object" }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/queue/add" + ], + "title": "Thread/queue/addRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadQueueAddParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/queue/addRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/queue/list" + ], + "title": "Thread/queue/listRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadQueueListParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/queue/listRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/queue/delete" + ], + "title": "Thread/queue/deleteRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadQueueDeleteParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/queue/deleteRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/queue/reorder" + ], + "title": "Thread/queue/reorderRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadQueueReorderParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/queue/reorderRequest", + "type": "object" + }, { "properties": { "id": { @@ -6206,4 +6361,4 @@ } ], "title": "ClientRequest" -} +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/ServerNotification.json b/codex-rs/app-server-protocol/schema/json/ServerNotification.json index 5dc3c09a44..3a077111d3 100644 --- a/codex-rs/app-server-protocol/schema/json/ServerNotification.json +++ b/codex-rs/app-server-protocol/schema/json/ServerNotification.json @@ -415,6 +415,65 @@ ], "type": "object" }, + "ApprovalsReviewer": { + "description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.", + "enum": [ + "user", + "auto_review", + "guardian_subagent" + ], + "type": "string" + }, + "AskForApproval": { + "oneOf": [ + { + "enum": [ + "untrusted", + "on-failure", + "on-request", + "never" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "granular": { + "properties": { + "mcp_elicitations": { + "type": "boolean" + }, + "request_permissions": { + "default": false, + "type": "boolean" + }, + "rules": { + "type": "boolean" + }, + "sandbox_approval": { + "type": "boolean" + }, + "skill_approval": { + "default": false, + "type": "boolean" + } + }, + "required": [ + "mcp_elicitations", + "rules", + "sandbox_approval" + ], + "type": "object" + } + }, + "required": [ + "granular" + ], + "title": "GranularAskForApproval", + "type": "object" + } + ] + }, "AuthMode": { "description": "Authentication mode for OpenAI-backed providers.", "oneOf": [ @@ -658,6 +717,22 @@ ], "type": "string" }, + "CollaborationMode": { + "description": "Collaboration mode for a Codex session.", + "properties": { + "mode": { + "$ref": "#/definitions/ModeKind" + }, + "settings": { + "$ref": "#/definitions/Settings" + } + }, + "required": [ + "mode", + "settings" + ], + "type": "object" + }, "CommandAction": { "oneOf": [ { @@ -2232,6 +2307,14 @@ } ] }, + "ModeKind": { + "description": "Initial collaboration mode to use when the TUI starts.", + "enum": [ + "plan", + "default" + ], + "type": "string" + }, "ModelRerouteReason": { "enum": [ "highRiskCyberActivity" @@ -2293,6 +2376,13 @@ ], "type": "object" }, + "NetworkAccess": { + "enum": [ + "restricted", + "enabled" + ], + "type": "string" + }, "NetworkApprovalProtocol": { "enum": [ "http", @@ -2376,6 +2466,73 @@ } ] }, + "PermissionProfileModificationParams": { + "oneOf": [ + { + "description": "Additional concrete directory that should be writable.", + "properties": { + "path": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": { + "enum": [ + "additionalWritableRoot" + ], + "title": "AdditionalWritableRootPermissionProfileModificationParamsType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "AdditionalWritableRootPermissionProfileModificationParams", + "type": "object" + } + ] + }, + "PermissionProfileSelectionParams": { + "oneOf": [ + { + "description": "Select a named built-in or user-defined profile and optionally apply bounded modifications that Codex knows how to validate.", + "properties": { + "id": { + "type": "string" + }, + "modifications": { + "items": { + "$ref": "#/definitions/PermissionProfileModificationParams" + }, + "type": [ + "array", + "null" + ] + }, + "type": { + "enum": [ + "profile" + ], + "title": "ProfilePermissionProfileSelectionParamsType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ProfilePermissionProfileSelectionParams", + "type": "object" + } + ] + }, + "Personality": { + "enum": [ + "none", + "friendly", + "pragmatic" + ], + "type": "string" + }, "PlanDeltaNotification": { "description": "EXPERIMENTAL - proposed plan streaming deltas for plan items. Clients should not assume concatenated deltas match the completed plan item content.", "properties": { @@ -2507,6 +2664,21 @@ } ] }, + "QueuedTurn": { + "properties": { + "id": { + "type": "string" + }, + "turnStartParams": { + "$ref": "#/definitions/TurnStartParams" + } + }, + "required": [ + "id", + "turnStartParams" + ], + "type": "object" + }, "RateLimitReachedType": { "enum": [ "rate_limit_reached", @@ -2629,6 +2801,26 @@ ], "type": "string" }, + "ReasoningSummary": { + "description": "A summary of the reasoning performed by the model. This can be useful for debugging and understanding the model's reasoning process. See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#reasoning-summaries", + "oneOf": [ + { + "enum": [ + "auto", + "concise", + "detailed" + ], + "type": "string" + }, + { + "description": "Option to disable reasoning summaries.", + "enum": [ + "none" + ], + "type": "string" + } + ] + }, "ReasoningSummaryPartAddedNotification": { "properties": { "itemId": { @@ -2773,6 +2965,105 @@ }, "type": "object" }, + "SandboxPolicy": { + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "dangerFullAccess" + ], + "title": "DangerFullAccessSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DangerFullAccessSandboxPolicy", + "type": "object" + }, + { + "properties": { + "networkAccess": { + "default": false, + "type": "boolean" + }, + "type": { + "enum": [ + "readOnly" + ], + "title": "ReadOnlySandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ReadOnlySandboxPolicy", + "type": "object" + }, + { + "properties": { + "networkAccess": { + "allOf": [ + { + "$ref": "#/definitions/NetworkAccess" + } + ], + "default": "restricted" + }, + "type": { + "enum": [ + "externalSandbox" + ], + "title": "ExternalSandboxSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ExternalSandboxSandboxPolicy", + "type": "object" + }, + { + "properties": { + "excludeSlashTmp": { + "default": false, + "type": "boolean" + }, + "excludeTmpdirEnvVar": { + "default": false, + "type": "boolean" + }, + "networkAccess": { + "default": false, + "type": "boolean" + }, + "type": { + "enum": [ + "workspaceWrite" + ], + "title": "WorkspaceWriteSandboxPolicyType", + "type": "string" + }, + "writableRoots": { + "default": [], + "items": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": "array" + } + }, + "required": [ + "type" + ], + "title": "WorkspaceWriteSandboxPolicy", + "type": "object" + } + ] + }, "ServerRequestResolvedNotification": { "properties": { "requestId": { @@ -2828,6 +3119,34 @@ } ] }, + "Settings": { + "description": "Settings for a collaboration mode.", + "properties": { + "developer_instructions": { + "type": [ + "string", + "null" + ] + }, + "model": { + "type": "string" + }, + "reasoning_effort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "model" + ], + "type": "object" + }, "SkillsChangedNotification": { "description": "Notification emitted when watched local skill files change.\n\nTreat this as an invalidation signal and re-run `skills/list` with the client's current parameters when refreshed skill metadata is needed.", "type": "object" @@ -3930,6 +4249,24 @@ ], "type": "object" }, + "ThreadQueueChangedNotification": { + "properties": { + "queuedTurns": { + "items": { + "$ref": "#/definitions/QueuedTurn" + }, + "type": "array" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "queuedTurns", + "threadId" + ], + "type": "object" + }, "ThreadRealtimeAudioChunk": { "description": "EXPERIMENTAL - thread realtime audio chunk.", "properties": { @@ -4407,6 +4744,21 @@ ], "type": "object" }, + "TurnEnvironmentParams": { + "properties": { + "cwd": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "environmentId": { + "type": "string" + } + }, + "required": [ + "cwd", + "environmentId" + ], + "type": "object" + }, "TurnError": { "properties": { "additionalDetails": { @@ -4511,6 +4863,114 @@ ], "type": "object" }, + "TurnStartParams": { + "properties": { + "approvalPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/AskForApproval" + }, + { + "type": "null" + } + ], + "description": "Override the approval policy for this turn and subsequent turns." + }, + "approvalsReviewer": { + "anyOf": [ + { + "$ref": "#/definitions/ApprovalsReviewer" + }, + { + "type": "null" + } + ], + "description": "Override where approval requests are routed for review on this turn and subsequent turns." + }, + "cwd": { + "description": "Override the working directory for this turn and subsequent turns.", + "type": [ + "string", + "null" + ] + }, + "effort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ], + "description": "Override the reasoning effort for this turn and subsequent turns." + }, + "input": { + "items": { + "$ref": "#/definitions/UserInput" + }, + "type": "array" + }, + "model": { + "description": "Override the model for this turn and subsequent turns.", + "type": [ + "string", + "null" + ] + }, + "outputSchema": { + "description": "Optional JSON Schema used to constrain the final assistant message for this turn." + }, + "personality": { + "anyOf": [ + { + "$ref": "#/definitions/Personality" + }, + { + "type": "null" + } + ], + "description": "Override the personality for this turn and subsequent turns." + }, + "sandboxPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxPolicy" + }, + { + "type": "null" + } + ], + "description": "Override the sandbox policy for this turn and subsequent turns." + }, + "serviceTier": { + "description": "Override the service tier for this turn and subsequent turns.", + "type": [ + "string", + "null" + ] + }, + "summary": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningSummary" + }, + { + "type": "null" + } + ], + "description": "Override the reasoning summary for this turn and subsequent turns." + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "input", + "threadId" + ], + "type": "object" + }, "TurnStartedNotification": { "properties": { "threadId": { @@ -5031,6 +5491,26 @@ "title": "Thread/goal/clearedNotification", "type": "object" }, + { + "properties": { + "method": { + "enum": [ + "thread/queue/changed" + ], + "title": "Thread/queue/changedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadQueueChangedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Thread/queue/changedNotification", + "type": "object" + }, { "properties": { "method": { diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index 5e24845729..72ba65ac91 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -353,6 +353,102 @@ "title": "Thread/name/setRequest", "type": "object" }, + { + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "enum": [ + "thread/queue/add" + ], + "title": "Thread/queue/addRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ThreadQueueAddParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/queue/addRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "enum": [ + "thread/queue/list" + ], + "title": "Thread/queue/listRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ThreadQueueListParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/queue/listRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "enum": [ + "thread/queue/delete" + ], + "title": "Thread/queue/deleteRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ThreadQueueDeleteParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/queue/deleteRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "enum": [ + "thread/queue/reorder" + ], + "title": "Thread/queue/reorderRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ThreadQueueReorderParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/queue/reorderRequest", + "type": "object" + }, { "properties": { "id": { @@ -3941,6 +4037,26 @@ "title": "Thread/goal/clearedNotification", "type": "object" }, + { + "properties": { + "method": { + "enum": [ + "thread/queue/changed" + ], + "title": "Thread/queue/changedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ThreadQueueChangedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Thread/queue/changedNotification", + "type": "object" + }, { "properties": { "method": { @@ -12852,6 +12968,21 @@ }, "type": "object" }, + "QueuedTurn": { + "properties": { + "id": { + "type": "string" + }, + "turnStartParams": { + "$ref": "#/definitions/v2/TurnStartParams" + } + }, + "required": [ + "id", + "turnStartParams" + ], + "type": "object" + }, "RateLimitReachedType": { "enum": [ "rate_limit_reached", @@ -16431,6 +16562,151 @@ "title": "ThreadNameUpdatedNotification", "type": "object" }, + "ThreadQueueAddParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "threadId": { + "type": "string" + }, + "turnStartParams": { + "$ref": "#/definitions/v2/TurnStartParams" + } + }, + "required": [ + "threadId", + "turnStartParams" + ], + "title": "ThreadQueueAddParams", + "type": "object" + }, + "ThreadQueueAddResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "queuedTurn": { + "$ref": "#/definitions/v2/QueuedTurn" + } + }, + "required": [ + "queuedTurn" + ], + "title": "ThreadQueueAddResponse", + "type": "object" + }, + "ThreadQueueChangedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "queuedTurns": { + "items": { + "$ref": "#/definitions/v2/QueuedTurn" + }, + "type": "array" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "queuedTurns", + "threadId" + ], + "title": "ThreadQueueChangedNotification", + "type": "object" + }, + "ThreadQueueDeleteParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "queuedTurnId": { + "type": "string" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "queuedTurnId", + "threadId" + ], + "title": "ThreadQueueDeleteParams", + "type": "object" + }, + "ThreadQueueDeleteResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "deleted": { + "type": "boolean" + } + }, + "required": [ + "deleted" + ], + "title": "ThreadQueueDeleteResponse", + "type": "object" + }, + "ThreadQueueListParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "title": "ThreadQueueListParams", + "type": "object" + }, + "ThreadQueueListResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "queuedTurns": { + "items": { + "$ref": "#/definitions/v2/QueuedTurn" + }, + "type": "array" + } + }, + "required": [ + "queuedTurns" + ], + "title": "ThreadQueueListResponse", + "type": "object" + }, + "ThreadQueueReorderParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "queuedTurnIds": { + "items": { + "type": "string" + }, + "type": "array" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "queuedTurnIds", + "threadId" + ], + "title": "ThreadQueueReorderParams", + "type": "object" + }, + "ThreadQueueReorderResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "queuedTurns": { + "items": { + "$ref": "#/definitions/v2/QueuedTurn" + }, + "type": "array" + } + }, + "required": [ + "queuedTurns" + ], + "title": "ThreadQueueReorderResponse", + "type": "object" + }, "ThreadReadParams": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { @@ -18389,4 +18665,4 @@ }, "title": "CodexAppServerProtocol", "type": "object" -} +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index 6153a54eb9..4ac102c19c 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -1112,6 +1112,102 @@ "title": "Thread/name/setRequest", "type": "object" }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/queue/add" + ], + "title": "Thread/queue/addRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadQueueAddParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/queue/addRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/queue/list" + ], + "title": "Thread/queue/listRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadQueueListParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/queue/listRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/queue/delete" + ], + "title": "Thread/queue/deleteRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadQueueDeleteParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/queue/deleteRequest", + "type": "object" + }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/queue/reorder" + ], + "title": "Thread/queue/reorderRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadQueueReorderParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/queue/reorderRequest", + "type": "object" + }, { "properties": { "id": { @@ -9463,6 +9559,21 @@ }, "type": "object" }, + "QueuedTurn": { + "properties": { + "id": { + "type": "string" + }, + "turnStartParams": { + "$ref": "#/definitions/TurnStartParams" + } + }, + "required": [ + "id", + "turnStartParams" + ], + "type": "object" + }, "RateLimitReachedType": { "enum": [ "rate_limit_reached", @@ -11140,6 +11251,26 @@ "title": "Thread/goal/clearedNotification", "type": "object" }, + { + "properties": { + "method": { + "enum": [ + "thread/queue/changed" + ], + "title": "Thread/queue/changedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadQueueChangedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Thread/queue/changedNotification", + "type": "object" + }, { "properties": { "method": { @@ -14317,6 +14448,151 @@ "title": "ThreadNameUpdatedNotification", "type": "object" }, + "ThreadQueueAddParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "threadId": { + "type": "string" + }, + "turnStartParams": { + "$ref": "#/definitions/TurnStartParams" + } + }, + "required": [ + "threadId", + "turnStartParams" + ], + "title": "ThreadQueueAddParams", + "type": "object" + }, + "ThreadQueueAddResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "queuedTurn": { + "$ref": "#/definitions/QueuedTurn" + } + }, + "required": [ + "queuedTurn" + ], + "title": "ThreadQueueAddResponse", + "type": "object" + }, + "ThreadQueueChangedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "queuedTurns": { + "items": { + "$ref": "#/definitions/QueuedTurn" + }, + "type": "array" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "queuedTurns", + "threadId" + ], + "title": "ThreadQueueChangedNotification", + "type": "object" + }, + "ThreadQueueDeleteParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "queuedTurnId": { + "type": "string" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "queuedTurnId", + "threadId" + ], + "title": "ThreadQueueDeleteParams", + "type": "object" + }, + "ThreadQueueDeleteResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "deleted": { + "type": "boolean" + } + }, + "required": [ + "deleted" + ], + "title": "ThreadQueueDeleteResponse", + "type": "object" + }, + "ThreadQueueListParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "title": "ThreadQueueListParams", + "type": "object" + }, + "ThreadQueueListResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "queuedTurns": { + "items": { + "$ref": "#/definitions/QueuedTurn" + }, + "type": "array" + } + }, + "required": [ + "queuedTurns" + ], + "title": "ThreadQueueListResponse", + "type": "object" + }, + "ThreadQueueReorderParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "queuedTurnIds": { + "items": { + "type": "string" + }, + "type": "array" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "queuedTurnIds", + "threadId" + ], + "title": "ThreadQueueReorderParams", + "type": "object" + }, + "ThreadQueueReorderResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "queuedTurns": { + "items": { + "$ref": "#/definitions/QueuedTurn" + }, + "type": "array" + } + }, + "required": [ + "queuedTurns" + ], + "title": "ThreadQueueReorderResponse", + "type": "object" + }, "ThreadReadParams": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { @@ -16274,4 +16550,4 @@ }, "title": "CodexAppServerProtocolV2", "type": "object" -} +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadQueueAddParams.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadQueueAddParams.json new file mode 100644 index 0000000000..a6db7591d3 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadQueueAddParams.json @@ -0,0 +1,624 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AbsolutePathBuf": { + "description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + "type": "string" + }, + "ApprovalsReviewer": { + "description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.", + "enum": [ + "user", + "auto_review", + "guardian_subagent" + ], + "type": "string" + }, + "AskForApproval": { + "oneOf": [ + { + "enum": [ + "untrusted", + "on-failure", + "on-request", + "never" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "granular": { + "properties": { + "mcp_elicitations": { + "type": "boolean" + }, + "request_permissions": { + "default": false, + "type": "boolean" + }, + "rules": { + "type": "boolean" + }, + "sandbox_approval": { + "type": "boolean" + }, + "skill_approval": { + "default": false, + "type": "boolean" + } + }, + "required": [ + "mcp_elicitations", + "rules", + "sandbox_approval" + ], + "type": "object" + } + }, + "required": [ + "granular" + ], + "title": "GranularAskForApproval", + "type": "object" + } + ] + }, + "ByteRange": { + "properties": { + "end": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "CollaborationMode": { + "description": "Collaboration mode for a Codex session.", + "properties": { + "mode": { + "$ref": "#/definitions/ModeKind" + }, + "settings": { + "$ref": "#/definitions/Settings" + } + }, + "required": [ + "mode", + "settings" + ], + "type": "object" + }, + "ModeKind": { + "description": "Initial collaboration mode to use when the TUI starts.", + "enum": [ + "plan", + "default" + ], + "type": "string" + }, + "NetworkAccess": { + "enum": [ + "restricted", + "enabled" + ], + "type": "string" + }, + "PermissionProfileModificationParams": { + "oneOf": [ + { + "description": "Additional concrete directory that should be writable.", + "properties": { + "path": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": { + "enum": [ + "additionalWritableRoot" + ], + "title": "AdditionalWritableRootPermissionProfileModificationParamsType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "AdditionalWritableRootPermissionProfileModificationParams", + "type": "object" + } + ] + }, + "PermissionProfileSelectionParams": { + "oneOf": [ + { + "description": "Select a named built-in or user-defined profile and optionally apply bounded modifications that Codex knows how to validate.", + "properties": { + "id": { + "type": "string" + }, + "modifications": { + "items": { + "$ref": "#/definitions/PermissionProfileModificationParams" + }, + "type": [ + "array", + "null" + ] + }, + "type": { + "enum": [ + "profile" + ], + "title": "ProfilePermissionProfileSelectionParamsType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ProfilePermissionProfileSelectionParams", + "type": "object" + } + ] + }, + "Personality": { + "enum": [ + "none", + "friendly", + "pragmatic" + ], + "type": "string" + }, + "ReasoningEffort": { + "description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning", + "enum": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh" + ], + "type": "string" + }, + "ReasoningSummary": { + "description": "A summary of the reasoning performed by the model. This can be useful for debugging and understanding the model's reasoning process. See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#reasoning-summaries", + "oneOf": [ + { + "enum": [ + "auto", + "concise", + "detailed" + ], + "type": "string" + }, + { + "description": "Option to disable reasoning summaries.", + "enum": [ + "none" + ], + "type": "string" + } + ] + }, + "SandboxPolicy": { + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "dangerFullAccess" + ], + "title": "DangerFullAccessSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DangerFullAccessSandboxPolicy", + "type": "object" + }, + { + "properties": { + "networkAccess": { + "default": false, + "type": "boolean" + }, + "type": { + "enum": [ + "readOnly" + ], + "title": "ReadOnlySandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ReadOnlySandboxPolicy", + "type": "object" + }, + { + "properties": { + "networkAccess": { + "allOf": [ + { + "$ref": "#/definitions/NetworkAccess" + } + ], + "default": "restricted" + }, + "type": { + "enum": [ + "externalSandbox" + ], + "title": "ExternalSandboxSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ExternalSandboxSandboxPolicy", + "type": "object" + }, + { + "properties": { + "excludeSlashTmp": { + "default": false, + "type": "boolean" + }, + "excludeTmpdirEnvVar": { + "default": false, + "type": "boolean" + }, + "networkAccess": { + "default": false, + "type": "boolean" + }, + "type": { + "enum": [ + "workspaceWrite" + ], + "title": "WorkspaceWriteSandboxPolicyType", + "type": "string" + }, + "writableRoots": { + "default": [], + "items": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": "array" + } + }, + "required": [ + "type" + ], + "title": "WorkspaceWriteSandboxPolicy", + "type": "object" + } + ] + }, + "Settings": { + "description": "Settings for a collaboration mode.", + "properties": { + "developer_instructions": { + "type": [ + "string", + "null" + ] + }, + "model": { + "type": "string" + }, + "reasoning_effort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "model" + ], + "type": "object" + }, + "TextElement": { + "properties": { + "byteRange": { + "allOf": [ + { + "$ref": "#/definitions/ByteRange" + } + ], + "description": "Byte range in the parent `text` buffer that this element occupies." + }, + "placeholder": { + "description": "Optional human-readable placeholder for the element, displayed in the UI.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "byteRange" + ], + "type": "object" + }, + "TurnEnvironmentParams": { + "properties": { + "cwd": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "environmentId": { + "type": "string" + } + }, + "required": [ + "cwd", + "environmentId" + ], + "type": "object" + }, + "TurnStartParams": { + "properties": { + "approvalPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/AskForApproval" + }, + { + "type": "null" + } + ], + "description": "Override the approval policy for this turn and subsequent turns." + }, + "approvalsReviewer": { + "anyOf": [ + { + "$ref": "#/definitions/ApprovalsReviewer" + }, + { + "type": "null" + } + ], + "description": "Override where approval requests are routed for review on this turn and subsequent turns." + }, + "cwd": { + "description": "Override the working directory for this turn and subsequent turns.", + "type": [ + "string", + "null" + ] + }, + "effort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ], + "description": "Override the reasoning effort for this turn and subsequent turns." + }, + "input": { + "items": { + "$ref": "#/definitions/UserInput" + }, + "type": "array" + }, + "model": { + "description": "Override the model for this turn and subsequent turns.", + "type": [ + "string", + "null" + ] + }, + "outputSchema": { + "description": "Optional JSON Schema used to constrain the final assistant message for this turn." + }, + "personality": { + "anyOf": [ + { + "$ref": "#/definitions/Personality" + }, + { + "type": "null" + } + ], + "description": "Override the personality for this turn and subsequent turns." + }, + "sandboxPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxPolicy" + }, + { + "type": "null" + } + ], + "description": "Override the sandbox policy for this turn and subsequent turns." + }, + "serviceTier": { + "description": "Override the service tier for this turn and subsequent turns.", + "type": [ + "string", + "null" + ] + }, + "summary": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningSummary" + }, + { + "type": "null" + } + ], + "description": "Override the reasoning summary for this turn and subsequent turns." + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "input", + "threadId" + ], + "type": "object" + }, + "UserInput": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `text` used to render or persist special elements.", + "items": { + "$ref": "#/definitions/TextElement" + }, + "type": "array" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextUserInputType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextUserInput", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "image" + ], + "title": "ImageUserInputType", + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "type", + "url" + ], + "title": "ImageUserInput", + "type": "object" + }, + { + "properties": { + "path": { + "type": "string" + }, + "type": { + "enum": [ + "localImage" + ], + "title": "LocalImageUserInputType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "LocalImageUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "skill" + ], + "title": "SkillUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "SkillUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "mention" + ], + "title": "MentionUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "MentionUserInput", + "type": "object" + } + ] + } + }, + "properties": { + "threadId": { + "type": "string" + }, + "turnStartParams": { + "$ref": "#/definitions/TurnStartParams" + } + }, + "required": [ + "threadId", + "turnStartParams" + ], + "title": "ThreadQueueAddParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadQueueAddResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadQueueAddResponse.json new file mode 100644 index 0000000000..c9db80bcee --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadQueueAddResponse.json @@ -0,0 +1,635 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AbsolutePathBuf": { + "description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + "type": "string" + }, + "ApprovalsReviewer": { + "description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.", + "enum": [ + "user", + "auto_review", + "guardian_subagent" + ], + "type": "string" + }, + "AskForApproval": { + "oneOf": [ + { + "enum": [ + "untrusted", + "on-failure", + "on-request", + "never" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "granular": { + "properties": { + "mcp_elicitations": { + "type": "boolean" + }, + "request_permissions": { + "default": false, + "type": "boolean" + }, + "rules": { + "type": "boolean" + }, + "sandbox_approval": { + "type": "boolean" + }, + "skill_approval": { + "default": false, + "type": "boolean" + } + }, + "required": [ + "mcp_elicitations", + "rules", + "sandbox_approval" + ], + "type": "object" + } + }, + "required": [ + "granular" + ], + "title": "GranularAskForApproval", + "type": "object" + } + ] + }, + "ByteRange": { + "properties": { + "end": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "CollaborationMode": { + "description": "Collaboration mode for a Codex session.", + "properties": { + "mode": { + "$ref": "#/definitions/ModeKind" + }, + "settings": { + "$ref": "#/definitions/Settings" + } + }, + "required": [ + "mode", + "settings" + ], + "type": "object" + }, + "ModeKind": { + "description": "Initial collaboration mode to use when the TUI starts.", + "enum": [ + "plan", + "default" + ], + "type": "string" + }, + "NetworkAccess": { + "enum": [ + "restricted", + "enabled" + ], + "type": "string" + }, + "PermissionProfileModificationParams": { + "oneOf": [ + { + "description": "Additional concrete directory that should be writable.", + "properties": { + "path": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": { + "enum": [ + "additionalWritableRoot" + ], + "title": "AdditionalWritableRootPermissionProfileModificationParamsType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "AdditionalWritableRootPermissionProfileModificationParams", + "type": "object" + } + ] + }, + "PermissionProfileSelectionParams": { + "oneOf": [ + { + "description": "Select a named built-in or user-defined profile and optionally apply bounded modifications that Codex knows how to validate.", + "properties": { + "id": { + "type": "string" + }, + "modifications": { + "items": { + "$ref": "#/definitions/PermissionProfileModificationParams" + }, + "type": [ + "array", + "null" + ] + }, + "type": { + "enum": [ + "profile" + ], + "title": "ProfilePermissionProfileSelectionParamsType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ProfilePermissionProfileSelectionParams", + "type": "object" + } + ] + }, + "Personality": { + "enum": [ + "none", + "friendly", + "pragmatic" + ], + "type": "string" + }, + "QueuedTurn": { + "properties": { + "id": { + "type": "string" + }, + "turnStartParams": { + "$ref": "#/definitions/TurnStartParams" + } + }, + "required": [ + "id", + "turnStartParams" + ], + "type": "object" + }, + "ReasoningEffort": { + "description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning", + "enum": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh" + ], + "type": "string" + }, + "ReasoningSummary": { + "description": "A summary of the reasoning performed by the model. This can be useful for debugging and understanding the model's reasoning process. See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#reasoning-summaries", + "oneOf": [ + { + "enum": [ + "auto", + "concise", + "detailed" + ], + "type": "string" + }, + { + "description": "Option to disable reasoning summaries.", + "enum": [ + "none" + ], + "type": "string" + } + ] + }, + "SandboxPolicy": { + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "dangerFullAccess" + ], + "title": "DangerFullAccessSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DangerFullAccessSandboxPolicy", + "type": "object" + }, + { + "properties": { + "networkAccess": { + "default": false, + "type": "boolean" + }, + "type": { + "enum": [ + "readOnly" + ], + "title": "ReadOnlySandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ReadOnlySandboxPolicy", + "type": "object" + }, + { + "properties": { + "networkAccess": { + "allOf": [ + { + "$ref": "#/definitions/NetworkAccess" + } + ], + "default": "restricted" + }, + "type": { + "enum": [ + "externalSandbox" + ], + "title": "ExternalSandboxSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ExternalSandboxSandboxPolicy", + "type": "object" + }, + { + "properties": { + "excludeSlashTmp": { + "default": false, + "type": "boolean" + }, + "excludeTmpdirEnvVar": { + "default": false, + "type": "boolean" + }, + "networkAccess": { + "default": false, + "type": "boolean" + }, + "type": { + "enum": [ + "workspaceWrite" + ], + "title": "WorkspaceWriteSandboxPolicyType", + "type": "string" + }, + "writableRoots": { + "default": [], + "items": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": "array" + } + }, + "required": [ + "type" + ], + "title": "WorkspaceWriteSandboxPolicy", + "type": "object" + } + ] + }, + "Settings": { + "description": "Settings for a collaboration mode.", + "properties": { + "developer_instructions": { + "type": [ + "string", + "null" + ] + }, + "model": { + "type": "string" + }, + "reasoning_effort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "model" + ], + "type": "object" + }, + "TextElement": { + "properties": { + "byteRange": { + "allOf": [ + { + "$ref": "#/definitions/ByteRange" + } + ], + "description": "Byte range in the parent `text` buffer that this element occupies." + }, + "placeholder": { + "description": "Optional human-readable placeholder for the element, displayed in the UI.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "byteRange" + ], + "type": "object" + }, + "TurnEnvironmentParams": { + "properties": { + "cwd": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "environmentId": { + "type": "string" + } + }, + "required": [ + "cwd", + "environmentId" + ], + "type": "object" + }, + "TurnStartParams": { + "properties": { + "approvalPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/AskForApproval" + }, + { + "type": "null" + } + ], + "description": "Override the approval policy for this turn and subsequent turns." + }, + "approvalsReviewer": { + "anyOf": [ + { + "$ref": "#/definitions/ApprovalsReviewer" + }, + { + "type": "null" + } + ], + "description": "Override where approval requests are routed for review on this turn and subsequent turns." + }, + "cwd": { + "description": "Override the working directory for this turn and subsequent turns.", + "type": [ + "string", + "null" + ] + }, + "effort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ], + "description": "Override the reasoning effort for this turn and subsequent turns." + }, + "input": { + "items": { + "$ref": "#/definitions/UserInput" + }, + "type": "array" + }, + "model": { + "description": "Override the model for this turn and subsequent turns.", + "type": [ + "string", + "null" + ] + }, + "outputSchema": { + "description": "Optional JSON Schema used to constrain the final assistant message for this turn." + }, + "personality": { + "anyOf": [ + { + "$ref": "#/definitions/Personality" + }, + { + "type": "null" + } + ], + "description": "Override the personality for this turn and subsequent turns." + }, + "sandboxPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxPolicy" + }, + { + "type": "null" + } + ], + "description": "Override the sandbox policy for this turn and subsequent turns." + }, + "serviceTier": { + "description": "Override the service tier for this turn and subsequent turns.", + "type": [ + "string", + "null" + ] + }, + "summary": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningSummary" + }, + { + "type": "null" + } + ], + "description": "Override the reasoning summary for this turn and subsequent turns." + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "input", + "threadId" + ], + "type": "object" + }, + "UserInput": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `text` used to render or persist special elements.", + "items": { + "$ref": "#/definitions/TextElement" + }, + "type": "array" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextUserInputType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextUserInput", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "image" + ], + "title": "ImageUserInputType", + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "type", + "url" + ], + "title": "ImageUserInput", + "type": "object" + }, + { + "properties": { + "path": { + "type": "string" + }, + "type": { + "enum": [ + "localImage" + ], + "title": "LocalImageUserInputType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "LocalImageUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "skill" + ], + "title": "SkillUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "SkillUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "mention" + ], + "title": "MentionUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "MentionUserInput", + "type": "object" + } + ] + } + }, + "properties": { + "queuedTurn": { + "$ref": "#/definitions/QueuedTurn" + } + }, + "required": [ + "queuedTurn" + ], + "title": "ThreadQueueAddResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadQueueChangedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadQueueChangedNotification.json new file mode 100644 index 0000000000..677eb85d17 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadQueueChangedNotification.json @@ -0,0 +1,642 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AbsolutePathBuf": { + "description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + "type": "string" + }, + "ApprovalsReviewer": { + "description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.", + "enum": [ + "user", + "auto_review", + "guardian_subagent" + ], + "type": "string" + }, + "AskForApproval": { + "oneOf": [ + { + "enum": [ + "untrusted", + "on-failure", + "on-request", + "never" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "granular": { + "properties": { + "mcp_elicitations": { + "type": "boolean" + }, + "request_permissions": { + "default": false, + "type": "boolean" + }, + "rules": { + "type": "boolean" + }, + "sandbox_approval": { + "type": "boolean" + }, + "skill_approval": { + "default": false, + "type": "boolean" + } + }, + "required": [ + "mcp_elicitations", + "rules", + "sandbox_approval" + ], + "type": "object" + } + }, + "required": [ + "granular" + ], + "title": "GranularAskForApproval", + "type": "object" + } + ] + }, + "ByteRange": { + "properties": { + "end": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "CollaborationMode": { + "description": "Collaboration mode for a Codex session.", + "properties": { + "mode": { + "$ref": "#/definitions/ModeKind" + }, + "settings": { + "$ref": "#/definitions/Settings" + } + }, + "required": [ + "mode", + "settings" + ], + "type": "object" + }, + "ModeKind": { + "description": "Initial collaboration mode to use when the TUI starts.", + "enum": [ + "plan", + "default" + ], + "type": "string" + }, + "NetworkAccess": { + "enum": [ + "restricted", + "enabled" + ], + "type": "string" + }, + "PermissionProfileModificationParams": { + "oneOf": [ + { + "description": "Additional concrete directory that should be writable.", + "properties": { + "path": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": { + "enum": [ + "additionalWritableRoot" + ], + "title": "AdditionalWritableRootPermissionProfileModificationParamsType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "AdditionalWritableRootPermissionProfileModificationParams", + "type": "object" + } + ] + }, + "PermissionProfileSelectionParams": { + "oneOf": [ + { + "description": "Select a named built-in or user-defined profile and optionally apply bounded modifications that Codex knows how to validate.", + "properties": { + "id": { + "type": "string" + }, + "modifications": { + "items": { + "$ref": "#/definitions/PermissionProfileModificationParams" + }, + "type": [ + "array", + "null" + ] + }, + "type": { + "enum": [ + "profile" + ], + "title": "ProfilePermissionProfileSelectionParamsType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ProfilePermissionProfileSelectionParams", + "type": "object" + } + ] + }, + "Personality": { + "enum": [ + "none", + "friendly", + "pragmatic" + ], + "type": "string" + }, + "QueuedTurn": { + "properties": { + "id": { + "type": "string" + }, + "turnStartParams": { + "$ref": "#/definitions/TurnStartParams" + } + }, + "required": [ + "id", + "turnStartParams" + ], + "type": "object" + }, + "ReasoningEffort": { + "description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning", + "enum": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh" + ], + "type": "string" + }, + "ReasoningSummary": { + "description": "A summary of the reasoning performed by the model. This can be useful for debugging and understanding the model's reasoning process. See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#reasoning-summaries", + "oneOf": [ + { + "enum": [ + "auto", + "concise", + "detailed" + ], + "type": "string" + }, + { + "description": "Option to disable reasoning summaries.", + "enum": [ + "none" + ], + "type": "string" + } + ] + }, + "SandboxPolicy": { + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "dangerFullAccess" + ], + "title": "DangerFullAccessSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DangerFullAccessSandboxPolicy", + "type": "object" + }, + { + "properties": { + "networkAccess": { + "default": false, + "type": "boolean" + }, + "type": { + "enum": [ + "readOnly" + ], + "title": "ReadOnlySandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ReadOnlySandboxPolicy", + "type": "object" + }, + { + "properties": { + "networkAccess": { + "allOf": [ + { + "$ref": "#/definitions/NetworkAccess" + } + ], + "default": "restricted" + }, + "type": { + "enum": [ + "externalSandbox" + ], + "title": "ExternalSandboxSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ExternalSandboxSandboxPolicy", + "type": "object" + }, + { + "properties": { + "excludeSlashTmp": { + "default": false, + "type": "boolean" + }, + "excludeTmpdirEnvVar": { + "default": false, + "type": "boolean" + }, + "networkAccess": { + "default": false, + "type": "boolean" + }, + "type": { + "enum": [ + "workspaceWrite" + ], + "title": "WorkspaceWriteSandboxPolicyType", + "type": "string" + }, + "writableRoots": { + "default": [], + "items": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": "array" + } + }, + "required": [ + "type" + ], + "title": "WorkspaceWriteSandboxPolicy", + "type": "object" + } + ] + }, + "Settings": { + "description": "Settings for a collaboration mode.", + "properties": { + "developer_instructions": { + "type": [ + "string", + "null" + ] + }, + "model": { + "type": "string" + }, + "reasoning_effort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "model" + ], + "type": "object" + }, + "TextElement": { + "properties": { + "byteRange": { + "allOf": [ + { + "$ref": "#/definitions/ByteRange" + } + ], + "description": "Byte range in the parent `text` buffer that this element occupies." + }, + "placeholder": { + "description": "Optional human-readable placeholder for the element, displayed in the UI.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "byteRange" + ], + "type": "object" + }, + "TurnEnvironmentParams": { + "properties": { + "cwd": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "environmentId": { + "type": "string" + } + }, + "required": [ + "cwd", + "environmentId" + ], + "type": "object" + }, + "TurnStartParams": { + "properties": { + "approvalPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/AskForApproval" + }, + { + "type": "null" + } + ], + "description": "Override the approval policy for this turn and subsequent turns." + }, + "approvalsReviewer": { + "anyOf": [ + { + "$ref": "#/definitions/ApprovalsReviewer" + }, + { + "type": "null" + } + ], + "description": "Override where approval requests are routed for review on this turn and subsequent turns." + }, + "cwd": { + "description": "Override the working directory for this turn and subsequent turns.", + "type": [ + "string", + "null" + ] + }, + "effort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ], + "description": "Override the reasoning effort for this turn and subsequent turns." + }, + "input": { + "items": { + "$ref": "#/definitions/UserInput" + }, + "type": "array" + }, + "model": { + "description": "Override the model for this turn and subsequent turns.", + "type": [ + "string", + "null" + ] + }, + "outputSchema": { + "description": "Optional JSON Schema used to constrain the final assistant message for this turn." + }, + "personality": { + "anyOf": [ + { + "$ref": "#/definitions/Personality" + }, + { + "type": "null" + } + ], + "description": "Override the personality for this turn and subsequent turns." + }, + "sandboxPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxPolicy" + }, + { + "type": "null" + } + ], + "description": "Override the sandbox policy for this turn and subsequent turns." + }, + "serviceTier": { + "description": "Override the service tier for this turn and subsequent turns.", + "type": [ + "string", + "null" + ] + }, + "summary": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningSummary" + }, + { + "type": "null" + } + ], + "description": "Override the reasoning summary for this turn and subsequent turns." + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "input", + "threadId" + ], + "type": "object" + }, + "UserInput": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `text` used to render or persist special elements.", + "items": { + "$ref": "#/definitions/TextElement" + }, + "type": "array" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextUserInputType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextUserInput", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "image" + ], + "title": "ImageUserInputType", + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "type", + "url" + ], + "title": "ImageUserInput", + "type": "object" + }, + { + "properties": { + "path": { + "type": "string" + }, + "type": { + "enum": [ + "localImage" + ], + "title": "LocalImageUserInputType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "LocalImageUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "skill" + ], + "title": "SkillUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "SkillUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "mention" + ], + "title": "MentionUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "MentionUserInput", + "type": "object" + } + ] + } + }, + "properties": { + "queuedTurns": { + "items": { + "$ref": "#/definitions/QueuedTurn" + }, + "type": "array" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "queuedTurns", + "threadId" + ], + "title": "ThreadQueueChangedNotification", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadQueueDeleteParams.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadQueueDeleteParams.json new file mode 100644 index 0000000000..a2155a69ad --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadQueueDeleteParams.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "queuedTurnId": { + "type": "string" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "queuedTurnId", + "threadId" + ], + "title": "ThreadQueueDeleteParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadQueueDeleteResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadQueueDeleteResponse.json new file mode 100644 index 0000000000..4d2ad389a0 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadQueueDeleteResponse.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "deleted": { + "type": "boolean" + } + }, + "required": [ + "deleted" + ], + "title": "ThreadQueueDeleteResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadQueueListParams.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadQueueListParams.json new file mode 100644 index 0000000000..6ae774a2d2 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadQueueListParams.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "title": "ThreadQueueListParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadQueueListResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadQueueListResponse.json new file mode 100644 index 0000000000..6bd6c4021b --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadQueueListResponse.json @@ -0,0 +1,638 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AbsolutePathBuf": { + "description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + "type": "string" + }, + "ApprovalsReviewer": { + "description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.", + "enum": [ + "user", + "auto_review", + "guardian_subagent" + ], + "type": "string" + }, + "AskForApproval": { + "oneOf": [ + { + "enum": [ + "untrusted", + "on-failure", + "on-request", + "never" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "granular": { + "properties": { + "mcp_elicitations": { + "type": "boolean" + }, + "request_permissions": { + "default": false, + "type": "boolean" + }, + "rules": { + "type": "boolean" + }, + "sandbox_approval": { + "type": "boolean" + }, + "skill_approval": { + "default": false, + "type": "boolean" + } + }, + "required": [ + "mcp_elicitations", + "rules", + "sandbox_approval" + ], + "type": "object" + } + }, + "required": [ + "granular" + ], + "title": "GranularAskForApproval", + "type": "object" + } + ] + }, + "ByteRange": { + "properties": { + "end": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "CollaborationMode": { + "description": "Collaboration mode for a Codex session.", + "properties": { + "mode": { + "$ref": "#/definitions/ModeKind" + }, + "settings": { + "$ref": "#/definitions/Settings" + } + }, + "required": [ + "mode", + "settings" + ], + "type": "object" + }, + "ModeKind": { + "description": "Initial collaboration mode to use when the TUI starts.", + "enum": [ + "plan", + "default" + ], + "type": "string" + }, + "NetworkAccess": { + "enum": [ + "restricted", + "enabled" + ], + "type": "string" + }, + "PermissionProfileModificationParams": { + "oneOf": [ + { + "description": "Additional concrete directory that should be writable.", + "properties": { + "path": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": { + "enum": [ + "additionalWritableRoot" + ], + "title": "AdditionalWritableRootPermissionProfileModificationParamsType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "AdditionalWritableRootPermissionProfileModificationParams", + "type": "object" + } + ] + }, + "PermissionProfileSelectionParams": { + "oneOf": [ + { + "description": "Select a named built-in or user-defined profile and optionally apply bounded modifications that Codex knows how to validate.", + "properties": { + "id": { + "type": "string" + }, + "modifications": { + "items": { + "$ref": "#/definitions/PermissionProfileModificationParams" + }, + "type": [ + "array", + "null" + ] + }, + "type": { + "enum": [ + "profile" + ], + "title": "ProfilePermissionProfileSelectionParamsType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ProfilePermissionProfileSelectionParams", + "type": "object" + } + ] + }, + "Personality": { + "enum": [ + "none", + "friendly", + "pragmatic" + ], + "type": "string" + }, + "QueuedTurn": { + "properties": { + "id": { + "type": "string" + }, + "turnStartParams": { + "$ref": "#/definitions/TurnStartParams" + } + }, + "required": [ + "id", + "turnStartParams" + ], + "type": "object" + }, + "ReasoningEffort": { + "description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning", + "enum": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh" + ], + "type": "string" + }, + "ReasoningSummary": { + "description": "A summary of the reasoning performed by the model. This can be useful for debugging and understanding the model's reasoning process. See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#reasoning-summaries", + "oneOf": [ + { + "enum": [ + "auto", + "concise", + "detailed" + ], + "type": "string" + }, + { + "description": "Option to disable reasoning summaries.", + "enum": [ + "none" + ], + "type": "string" + } + ] + }, + "SandboxPolicy": { + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "dangerFullAccess" + ], + "title": "DangerFullAccessSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DangerFullAccessSandboxPolicy", + "type": "object" + }, + { + "properties": { + "networkAccess": { + "default": false, + "type": "boolean" + }, + "type": { + "enum": [ + "readOnly" + ], + "title": "ReadOnlySandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ReadOnlySandboxPolicy", + "type": "object" + }, + { + "properties": { + "networkAccess": { + "allOf": [ + { + "$ref": "#/definitions/NetworkAccess" + } + ], + "default": "restricted" + }, + "type": { + "enum": [ + "externalSandbox" + ], + "title": "ExternalSandboxSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ExternalSandboxSandboxPolicy", + "type": "object" + }, + { + "properties": { + "excludeSlashTmp": { + "default": false, + "type": "boolean" + }, + "excludeTmpdirEnvVar": { + "default": false, + "type": "boolean" + }, + "networkAccess": { + "default": false, + "type": "boolean" + }, + "type": { + "enum": [ + "workspaceWrite" + ], + "title": "WorkspaceWriteSandboxPolicyType", + "type": "string" + }, + "writableRoots": { + "default": [], + "items": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": "array" + } + }, + "required": [ + "type" + ], + "title": "WorkspaceWriteSandboxPolicy", + "type": "object" + } + ] + }, + "Settings": { + "description": "Settings for a collaboration mode.", + "properties": { + "developer_instructions": { + "type": [ + "string", + "null" + ] + }, + "model": { + "type": "string" + }, + "reasoning_effort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "model" + ], + "type": "object" + }, + "TextElement": { + "properties": { + "byteRange": { + "allOf": [ + { + "$ref": "#/definitions/ByteRange" + } + ], + "description": "Byte range in the parent `text` buffer that this element occupies." + }, + "placeholder": { + "description": "Optional human-readable placeholder for the element, displayed in the UI.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "byteRange" + ], + "type": "object" + }, + "TurnEnvironmentParams": { + "properties": { + "cwd": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "environmentId": { + "type": "string" + } + }, + "required": [ + "cwd", + "environmentId" + ], + "type": "object" + }, + "TurnStartParams": { + "properties": { + "approvalPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/AskForApproval" + }, + { + "type": "null" + } + ], + "description": "Override the approval policy for this turn and subsequent turns." + }, + "approvalsReviewer": { + "anyOf": [ + { + "$ref": "#/definitions/ApprovalsReviewer" + }, + { + "type": "null" + } + ], + "description": "Override where approval requests are routed for review on this turn and subsequent turns." + }, + "cwd": { + "description": "Override the working directory for this turn and subsequent turns.", + "type": [ + "string", + "null" + ] + }, + "effort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ], + "description": "Override the reasoning effort for this turn and subsequent turns." + }, + "input": { + "items": { + "$ref": "#/definitions/UserInput" + }, + "type": "array" + }, + "model": { + "description": "Override the model for this turn and subsequent turns.", + "type": [ + "string", + "null" + ] + }, + "outputSchema": { + "description": "Optional JSON Schema used to constrain the final assistant message for this turn." + }, + "personality": { + "anyOf": [ + { + "$ref": "#/definitions/Personality" + }, + { + "type": "null" + } + ], + "description": "Override the personality for this turn and subsequent turns." + }, + "sandboxPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxPolicy" + }, + { + "type": "null" + } + ], + "description": "Override the sandbox policy for this turn and subsequent turns." + }, + "serviceTier": { + "description": "Override the service tier for this turn and subsequent turns.", + "type": [ + "string", + "null" + ] + }, + "summary": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningSummary" + }, + { + "type": "null" + } + ], + "description": "Override the reasoning summary for this turn and subsequent turns." + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "input", + "threadId" + ], + "type": "object" + }, + "UserInput": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `text` used to render or persist special elements.", + "items": { + "$ref": "#/definitions/TextElement" + }, + "type": "array" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextUserInputType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextUserInput", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "image" + ], + "title": "ImageUserInputType", + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "type", + "url" + ], + "title": "ImageUserInput", + "type": "object" + }, + { + "properties": { + "path": { + "type": "string" + }, + "type": { + "enum": [ + "localImage" + ], + "title": "LocalImageUserInputType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "LocalImageUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "skill" + ], + "title": "SkillUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "SkillUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "mention" + ], + "title": "MentionUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "MentionUserInput", + "type": "object" + } + ] + } + }, + "properties": { + "queuedTurns": { + "items": { + "$ref": "#/definitions/QueuedTurn" + }, + "type": "array" + } + }, + "required": [ + "queuedTurns" + ], + "title": "ThreadQueueListResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadQueueReorderParams.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadQueueReorderParams.json new file mode 100644 index 0000000000..4b7c488bca --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadQueueReorderParams.json @@ -0,0 +1,20 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "queuedTurnIds": { + "items": { + "type": "string" + }, + "type": "array" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "queuedTurnIds", + "threadId" + ], + "title": "ThreadQueueReorderParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadQueueReorderResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadQueueReorderResponse.json new file mode 100644 index 0000000000..afa48418ae --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadQueueReorderResponse.json @@ -0,0 +1,638 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AbsolutePathBuf": { + "description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + "type": "string" + }, + "ApprovalsReviewer": { + "description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.", + "enum": [ + "user", + "auto_review", + "guardian_subagent" + ], + "type": "string" + }, + "AskForApproval": { + "oneOf": [ + { + "enum": [ + "untrusted", + "on-failure", + "on-request", + "never" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "granular": { + "properties": { + "mcp_elicitations": { + "type": "boolean" + }, + "request_permissions": { + "default": false, + "type": "boolean" + }, + "rules": { + "type": "boolean" + }, + "sandbox_approval": { + "type": "boolean" + }, + "skill_approval": { + "default": false, + "type": "boolean" + } + }, + "required": [ + "mcp_elicitations", + "rules", + "sandbox_approval" + ], + "type": "object" + } + }, + "required": [ + "granular" + ], + "title": "GranularAskForApproval", + "type": "object" + } + ] + }, + "ByteRange": { + "properties": { + "end": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "CollaborationMode": { + "description": "Collaboration mode for a Codex session.", + "properties": { + "mode": { + "$ref": "#/definitions/ModeKind" + }, + "settings": { + "$ref": "#/definitions/Settings" + } + }, + "required": [ + "mode", + "settings" + ], + "type": "object" + }, + "ModeKind": { + "description": "Initial collaboration mode to use when the TUI starts.", + "enum": [ + "plan", + "default" + ], + "type": "string" + }, + "NetworkAccess": { + "enum": [ + "restricted", + "enabled" + ], + "type": "string" + }, + "PermissionProfileModificationParams": { + "oneOf": [ + { + "description": "Additional concrete directory that should be writable.", + "properties": { + "path": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": { + "enum": [ + "additionalWritableRoot" + ], + "title": "AdditionalWritableRootPermissionProfileModificationParamsType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "AdditionalWritableRootPermissionProfileModificationParams", + "type": "object" + } + ] + }, + "PermissionProfileSelectionParams": { + "oneOf": [ + { + "description": "Select a named built-in or user-defined profile and optionally apply bounded modifications that Codex knows how to validate.", + "properties": { + "id": { + "type": "string" + }, + "modifications": { + "items": { + "$ref": "#/definitions/PermissionProfileModificationParams" + }, + "type": [ + "array", + "null" + ] + }, + "type": { + "enum": [ + "profile" + ], + "title": "ProfilePermissionProfileSelectionParamsType", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ProfilePermissionProfileSelectionParams", + "type": "object" + } + ] + }, + "Personality": { + "enum": [ + "none", + "friendly", + "pragmatic" + ], + "type": "string" + }, + "QueuedTurn": { + "properties": { + "id": { + "type": "string" + }, + "turnStartParams": { + "$ref": "#/definitions/TurnStartParams" + } + }, + "required": [ + "id", + "turnStartParams" + ], + "type": "object" + }, + "ReasoningEffort": { + "description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning", + "enum": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh" + ], + "type": "string" + }, + "ReasoningSummary": { + "description": "A summary of the reasoning performed by the model. This can be useful for debugging and understanding the model's reasoning process. See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#reasoning-summaries", + "oneOf": [ + { + "enum": [ + "auto", + "concise", + "detailed" + ], + "type": "string" + }, + { + "description": "Option to disable reasoning summaries.", + "enum": [ + "none" + ], + "type": "string" + } + ] + }, + "SandboxPolicy": { + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "dangerFullAccess" + ], + "title": "DangerFullAccessSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "DangerFullAccessSandboxPolicy", + "type": "object" + }, + { + "properties": { + "networkAccess": { + "default": false, + "type": "boolean" + }, + "type": { + "enum": [ + "readOnly" + ], + "title": "ReadOnlySandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ReadOnlySandboxPolicy", + "type": "object" + }, + { + "properties": { + "networkAccess": { + "allOf": [ + { + "$ref": "#/definitions/NetworkAccess" + } + ], + "default": "restricted" + }, + "type": { + "enum": [ + "externalSandbox" + ], + "title": "ExternalSandboxSandboxPolicyType", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ExternalSandboxSandboxPolicy", + "type": "object" + }, + { + "properties": { + "excludeSlashTmp": { + "default": false, + "type": "boolean" + }, + "excludeTmpdirEnvVar": { + "default": false, + "type": "boolean" + }, + "networkAccess": { + "default": false, + "type": "boolean" + }, + "type": { + "enum": [ + "workspaceWrite" + ], + "title": "WorkspaceWriteSandboxPolicyType", + "type": "string" + }, + "writableRoots": { + "default": [], + "items": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": "array" + } + }, + "required": [ + "type" + ], + "title": "WorkspaceWriteSandboxPolicy", + "type": "object" + } + ] + }, + "Settings": { + "description": "Settings for a collaboration mode.", + "properties": { + "developer_instructions": { + "type": [ + "string", + "null" + ] + }, + "model": { + "type": "string" + }, + "reasoning_effort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "model" + ], + "type": "object" + }, + "TextElement": { + "properties": { + "byteRange": { + "allOf": [ + { + "$ref": "#/definitions/ByteRange" + } + ], + "description": "Byte range in the parent `text` buffer that this element occupies." + }, + "placeholder": { + "description": "Optional human-readable placeholder for the element, displayed in the UI.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "byteRange" + ], + "type": "object" + }, + "TurnEnvironmentParams": { + "properties": { + "cwd": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "environmentId": { + "type": "string" + } + }, + "required": [ + "cwd", + "environmentId" + ], + "type": "object" + }, + "TurnStartParams": { + "properties": { + "approvalPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/AskForApproval" + }, + { + "type": "null" + } + ], + "description": "Override the approval policy for this turn and subsequent turns." + }, + "approvalsReviewer": { + "anyOf": [ + { + "$ref": "#/definitions/ApprovalsReviewer" + }, + { + "type": "null" + } + ], + "description": "Override where approval requests are routed for review on this turn and subsequent turns." + }, + "cwd": { + "description": "Override the working directory for this turn and subsequent turns.", + "type": [ + "string", + "null" + ] + }, + "effort": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningEffort" + }, + { + "type": "null" + } + ], + "description": "Override the reasoning effort for this turn and subsequent turns." + }, + "input": { + "items": { + "$ref": "#/definitions/UserInput" + }, + "type": "array" + }, + "model": { + "description": "Override the model for this turn and subsequent turns.", + "type": [ + "string", + "null" + ] + }, + "outputSchema": { + "description": "Optional JSON Schema used to constrain the final assistant message for this turn." + }, + "personality": { + "anyOf": [ + { + "$ref": "#/definitions/Personality" + }, + { + "type": "null" + } + ], + "description": "Override the personality for this turn and subsequent turns." + }, + "sandboxPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/SandboxPolicy" + }, + { + "type": "null" + } + ], + "description": "Override the sandbox policy for this turn and subsequent turns." + }, + "serviceTier": { + "description": "Override the service tier for this turn and subsequent turns.", + "type": [ + "string", + "null" + ] + }, + "summary": { + "anyOf": [ + { + "$ref": "#/definitions/ReasoningSummary" + }, + { + "type": "null" + } + ], + "description": "Override the reasoning summary for this turn and subsequent turns." + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "input", + "threadId" + ], + "type": "object" + }, + "UserInput": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `text` used to render or persist special elements.", + "items": { + "$ref": "#/definitions/TextElement" + }, + "type": "array" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextUserInputType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextUserInput", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "image" + ], + "title": "ImageUserInputType", + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "type", + "url" + ], + "title": "ImageUserInput", + "type": "object" + }, + { + "properties": { + "path": { + "type": "string" + }, + "type": { + "enum": [ + "localImage" + ], + "title": "LocalImageUserInputType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "LocalImageUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "skill" + ], + "title": "SkillUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "SkillUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "mention" + ], + "title": "MentionUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "MentionUserInput", + "type": "object" + } + ] + } + }, + "properties": { + "queuedTurns": { + "items": { + "$ref": "#/definitions/QueuedTurn" + }, + "type": "array" + } + }, + "required": [ + "queuedTurns" + ], + "title": "ThreadQueueReorderResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts b/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts index a12185b501..f1e2381ff3 100644 --- a/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts +++ b/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts @@ -63,6 +63,10 @@ import type { ThreadInjectItemsParams } from "./v2/ThreadInjectItemsParams"; import type { ThreadListParams } from "./v2/ThreadListParams"; import type { ThreadLoadedListParams } from "./v2/ThreadLoadedListParams"; import type { ThreadMetadataUpdateParams } from "./v2/ThreadMetadataUpdateParams"; +import type { ThreadQueueAddParams } from "./v2/ThreadQueueAddParams"; +import type { ThreadQueueDeleteParams } from "./v2/ThreadQueueDeleteParams"; +import type { ThreadQueueListParams } from "./v2/ThreadQueueListParams"; +import type { ThreadQueueReorderParams } from "./v2/ThreadQueueReorderParams"; import type { ThreadReadParams } from "./v2/ThreadReadParams"; import type { ThreadResumeParams } from "./v2/ThreadResumeParams"; import type { ThreadRollbackParams } from "./v2/ThreadRollbackParams"; @@ -79,4 +83,4 @@ import type { WindowsSandboxSetupStartParams } from "./v2/WindowsSandboxSetupSta /** * Request from the client to the server. */ -export type ClientRequest ={ "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/unsubscribe", id: RequestId, params: ThreadUnsubscribeParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/metadata/update", id: RequestId, params: ThreadMetadataUpdateParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/compact/start", id: RequestId, params: ThreadCompactStartParams, } | { "method": "thread/shellCommand", id: RequestId, params: ThreadShellCommandParams, } | { "method": "thread/approveGuardianDeniedAction", id: RequestId, params: ThreadApproveGuardianDeniedActionParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "thread/inject_items", id: RequestId, params: ThreadInjectItemsParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "hooks/list", id: RequestId, params: HooksListParams, } | { "method": "marketplace/add", id: RequestId, params: MarketplaceAddParams, } | { "method": "marketplace/remove", id: RequestId, params: MarketplaceRemoveParams, } | { "method": "marketplace/upgrade", id: RequestId, params: MarketplaceUpgradeParams, } | { "method": "plugin/list", id: RequestId, params: PluginListParams, } | { "method": "plugin/read", id: RequestId, params: PluginReadParams, } | { "method": "plugin/skill/read", id: RequestId, params: PluginSkillReadParams, } | { "method": "plugin/share/save", id: RequestId, params: PluginShareSaveParams, } | { "method": "plugin/share/updateTargets", id: RequestId, params: PluginShareUpdateTargetsParams, } | { "method": "plugin/share/list", id: RequestId, params: PluginShareListParams, } | { "method": "plugin/share/delete", id: RequestId, params: PluginShareDeleteParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "fs/readFile", id: RequestId, params: FsReadFileParams, } | { "method": "fs/writeFile", id: RequestId, params: FsWriteFileParams, } | { "method": "fs/createDirectory", id: RequestId, params: FsCreateDirectoryParams, } | { "method": "fs/getMetadata", id: RequestId, params: FsGetMetadataParams, } | { "method": "fs/readDirectory", id: RequestId, params: FsReadDirectoryParams, } | { "method": "fs/remove", id: RequestId, params: FsRemoveParams, } | { "method": "fs/copy", id: RequestId, params: FsCopyParams, } | { "method": "fs/watch", id: RequestId, params: FsWatchParams, } | { "method": "fs/unwatch", id: RequestId, params: FsUnwatchParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "plugin/install", id: RequestId, params: PluginInstallParams, } | { "method": "plugin/uninstall", id: RequestId, params: PluginUninstallParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/steer", id: RequestId, params: TurnSteerParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "modelProvider/capabilities/read", id: RequestId, params: ModelProviderCapabilitiesReadParams, } | { "method": "experimentalFeature/list", id: RequestId, params: ExperimentalFeatureListParams, } | { "method": "experimentalFeature/enablement/set", id: RequestId, params: ExperimentalFeatureEnablementSetParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "mcpServer/resource/read", id: RequestId, params: McpResourceReadParams, } | { "method": "mcpServer/tool/call", id: RequestId, params: McpServerToolCallParams, } | { "method": "windowsSandbox/setupStart", id: RequestId, params: WindowsSandboxSetupStartParams, } | { "method": "windowsSandbox/readiness", id: RequestId, params: undefined, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "account/sendAddCreditsNudgeEmail", id: RequestId, params: SendAddCreditsNudgeEmailParams, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "command/exec/write", id: RequestId, params: CommandExecWriteParams, } | { "method": "command/exec/terminate", id: RequestId, params: CommandExecTerminateParams, } | { "method": "command/exec/resize", id: RequestId, params: CommandExecResizeParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "externalAgentConfig/detect", id: RequestId, params: ExternalAgentConfigDetectParams, } | { "method": "externalAgentConfig/import", id: RequestId, params: ExternalAgentConfigImportParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, }; +export type ClientRequest ={ "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/unsubscribe", id: RequestId, params: ThreadUnsubscribeParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/queue/add", id: RequestId, params: ThreadQueueAddParams, } | { "method": "thread/queue/list", id: RequestId, params: ThreadQueueListParams, } | { "method": "thread/queue/delete", id: RequestId, params: ThreadQueueDeleteParams, } | { "method": "thread/queue/reorder", id: RequestId, params: ThreadQueueReorderParams, } | { "method": "thread/metadata/update", id: RequestId, params: ThreadMetadataUpdateParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/compact/start", id: RequestId, params: ThreadCompactStartParams, } | { "method": "thread/shellCommand", id: RequestId, params: ThreadShellCommandParams, } | { "method": "thread/approveGuardianDeniedAction", id: RequestId, params: ThreadApproveGuardianDeniedActionParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "thread/inject_items", id: RequestId, params: ThreadInjectItemsParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "hooks/list", id: RequestId, params: HooksListParams, } | { "method": "marketplace/add", id: RequestId, params: MarketplaceAddParams, } | { "method": "marketplace/remove", id: RequestId, params: MarketplaceRemoveParams, } | { "method": "marketplace/upgrade", id: RequestId, params: MarketplaceUpgradeParams, } | { "method": "plugin/list", id: RequestId, params: PluginListParams, } | { "method": "plugin/read", id: RequestId, params: PluginReadParams, } | { "method": "plugin/skill/read", id: RequestId, params: PluginSkillReadParams, } | { "method": "plugin/share/save", id: RequestId, params: PluginShareSaveParams, } | { "method": "plugin/share/updateTargets", id: RequestId, params: PluginShareUpdateTargetsParams, } | { "method": "plugin/share/list", id: RequestId, params: PluginShareListParams, } | { "method": "plugin/share/delete", id: RequestId, params: PluginShareDeleteParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "fs/readFile", id: RequestId, params: FsReadFileParams, } | { "method": "fs/writeFile", id: RequestId, params: FsWriteFileParams, } | { "method": "fs/createDirectory", id: RequestId, params: FsCreateDirectoryParams, } | { "method": "fs/getMetadata", id: RequestId, params: FsGetMetadataParams, } | { "method": "fs/readDirectory", id: RequestId, params: FsReadDirectoryParams, } | { "method": "fs/remove", id: RequestId, params: FsRemoveParams, } | { "method": "fs/copy", id: RequestId, params: FsCopyParams, } | { "method": "fs/watch", id: RequestId, params: FsWatchParams, } | { "method": "fs/unwatch", id: RequestId, params: FsUnwatchParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "plugin/install", id: RequestId, params: PluginInstallParams, } | { "method": "plugin/uninstall", id: RequestId, params: PluginUninstallParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/steer", id: RequestId, params: TurnSteerParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "modelProvider/capabilities/read", id: RequestId, params: ModelProviderCapabilitiesReadParams, } | { "method": "experimentalFeature/list", id: RequestId, params: ExperimentalFeatureListParams, } | { "method": "experimentalFeature/enablement/set", id: RequestId, params: ExperimentalFeatureEnablementSetParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "mcpServer/resource/read", id: RequestId, params: McpResourceReadParams, } | { "method": "mcpServer/tool/call", id: RequestId, params: McpServerToolCallParams, } | { "method": "windowsSandbox/setupStart", id: RequestId, params: WindowsSandboxSetupStartParams, } | { "method": "windowsSandbox/readiness", id: RequestId, params: undefined, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "account/sendAddCreditsNudgeEmail", id: RequestId, params: SendAddCreditsNudgeEmailParams, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "command/exec/write", id: RequestId, params: CommandExecWriteParams, } | { "method": "command/exec/terminate", id: RequestId, params: CommandExecTerminateParams, } | { "method": "command/exec/resize", id: RequestId, params: CommandExecResizeParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "externalAgentConfig/detect", id: RequestId, params: ExternalAgentConfigDetectParams, } | { "method": "externalAgentConfig/import", id: RequestId, params: ExternalAgentConfigImportParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ServerNotification.ts b/codex-rs/app-server-protocol/schema/typescript/ServerNotification.ts index f4dd0e1864..f5c933654f 100644 --- a/codex-rs/app-server-protocol/schema/typescript/ServerNotification.ts +++ b/codex-rs/app-server-protocol/schema/typescript/ServerNotification.ts @@ -46,6 +46,7 @@ import type { ThreadClosedNotification } from "./v2/ThreadClosedNotification"; import type { ThreadGoalClearedNotification } from "./v2/ThreadGoalClearedNotification"; import type { ThreadGoalUpdatedNotification } from "./v2/ThreadGoalUpdatedNotification"; import type { ThreadNameUpdatedNotification } from "./v2/ThreadNameUpdatedNotification"; +import type { ThreadQueueChangedNotification } from "./v2/ThreadQueueChangedNotification"; import type { ThreadRealtimeClosedNotification } from "./v2/ThreadRealtimeClosedNotification"; import type { ThreadRealtimeErrorNotification } from "./v2/ThreadRealtimeErrorNotification"; import type { ThreadRealtimeItemAddedNotification } from "./v2/ThreadRealtimeItemAddedNotification"; @@ -69,4 +70,4 @@ import type { WindowsWorldWritableWarningNotification } from "./v2/WindowsWorldW /** * Notification sent from the server to the client. */ -export type ServerNotification = { "method": "error", "params": ErrorNotification } | { "method": "thread/started", "params": ThreadStartedNotification } | { "method": "thread/status/changed", "params": ThreadStatusChangedNotification } | { "method": "thread/archived", "params": ThreadArchivedNotification } | { "method": "thread/unarchived", "params": ThreadUnarchivedNotification } | { "method": "thread/closed", "params": ThreadClosedNotification } | { "method": "skills/changed", "params": SkillsChangedNotification } | { "method": "thread/name/updated", "params": ThreadNameUpdatedNotification } | { "method": "thread/goal/updated", "params": ThreadGoalUpdatedNotification } | { "method": "thread/goal/cleared", "params": ThreadGoalClearedNotification } | { "method": "thread/tokenUsage/updated", "params": ThreadTokenUsageUpdatedNotification } | { "method": "turn/started", "params": TurnStartedNotification } | { "method": "hook/started", "params": HookStartedNotification } | { "method": "turn/completed", "params": TurnCompletedNotification } | { "method": "hook/completed", "params": HookCompletedNotification } | { "method": "turn/diff/updated", "params": TurnDiffUpdatedNotification } | { "method": "turn/plan/updated", "params": TurnPlanUpdatedNotification } | { "method": "item/started", "params": ItemStartedNotification } | { "method": "item/autoApprovalReview/started", "params": ItemGuardianApprovalReviewStartedNotification } | { "method": "item/autoApprovalReview/completed", "params": ItemGuardianApprovalReviewCompletedNotification } | { "method": "item/completed", "params": ItemCompletedNotification } | { "method": "rawResponseItem/completed", "params": RawResponseItemCompletedNotification } | { "method": "item/agentMessage/delta", "params": AgentMessageDeltaNotification } | { "method": "item/plan/delta", "params": PlanDeltaNotification } | { "method": "command/exec/outputDelta", "params": CommandExecOutputDeltaNotification } | { "method": "process/outputDelta", "params": ProcessOutputDeltaNotification } | { "method": "process/exited", "params": ProcessExitedNotification } | { "method": "item/commandExecution/outputDelta", "params": CommandExecutionOutputDeltaNotification } | { "method": "item/commandExecution/terminalInteraction", "params": TerminalInteractionNotification } | { "method": "item/fileChange/outputDelta", "params": FileChangeOutputDeltaNotification } | { "method": "item/fileChange/patchUpdated", "params": FileChangePatchUpdatedNotification } | { "method": "serverRequest/resolved", "params": ServerRequestResolvedNotification } | { "method": "item/mcpToolCall/progress", "params": McpToolCallProgressNotification } | { "method": "mcpServer/oauthLogin/completed", "params": McpServerOauthLoginCompletedNotification } | { "method": "mcpServer/startupStatus/updated", "params": McpServerStatusUpdatedNotification } | { "method": "account/updated", "params": AccountUpdatedNotification } | { "method": "account/rateLimits/updated", "params": AccountRateLimitsUpdatedNotification } | { "method": "app/list/updated", "params": AppListUpdatedNotification } | { "method": "remoteControl/status/changed", "params": RemoteControlStatusChangedNotification } | { "method": "externalAgentConfig/import/completed", "params": ExternalAgentConfigImportCompletedNotification } | { "method": "fs/changed", "params": FsChangedNotification } | { "method": "item/reasoning/summaryTextDelta", "params": ReasoningSummaryTextDeltaNotification } | { "method": "item/reasoning/summaryPartAdded", "params": ReasoningSummaryPartAddedNotification } | { "method": "item/reasoning/textDelta", "params": ReasoningTextDeltaNotification } | { "method": "thread/compacted", "params": ContextCompactedNotification } | { "method": "model/rerouted", "params": ModelReroutedNotification } | { "method": "model/verification", "params": ModelVerificationNotification } | { "method": "warning", "params": WarningNotification } | { "method": "guardianWarning", "params": GuardianWarningNotification } | { "method": "deprecationNotice", "params": DeprecationNoticeNotification } | { "method": "configWarning", "params": ConfigWarningNotification } | { "method": "fuzzyFileSearch/sessionUpdated", "params": FuzzyFileSearchSessionUpdatedNotification } | { "method": "fuzzyFileSearch/sessionCompleted", "params": FuzzyFileSearchSessionCompletedNotification } | { "method": "thread/realtime/started", "params": ThreadRealtimeStartedNotification } | { "method": "thread/realtime/itemAdded", "params": ThreadRealtimeItemAddedNotification } | { "method": "thread/realtime/transcript/delta", "params": ThreadRealtimeTranscriptDeltaNotification } | { "method": "thread/realtime/transcript/done", "params": ThreadRealtimeTranscriptDoneNotification } | { "method": "thread/realtime/outputAudio/delta", "params": ThreadRealtimeOutputAudioDeltaNotification } | { "method": "thread/realtime/sdp", "params": ThreadRealtimeSdpNotification } | { "method": "thread/realtime/error", "params": ThreadRealtimeErrorNotification } | { "method": "thread/realtime/closed", "params": ThreadRealtimeClosedNotification } | { "method": "windows/worldWritableWarning", "params": WindowsWorldWritableWarningNotification } | { "method": "windowsSandbox/setupCompleted", "params": WindowsSandboxSetupCompletedNotification } | { "method": "account/login/completed", "params": AccountLoginCompletedNotification }; +export type ServerNotification = { "method": "error", "params": ErrorNotification } | { "method": "thread/started", "params": ThreadStartedNotification } | { "method": "thread/status/changed", "params": ThreadStatusChangedNotification } | { "method": "thread/archived", "params": ThreadArchivedNotification } | { "method": "thread/unarchived", "params": ThreadUnarchivedNotification } | { "method": "thread/closed", "params": ThreadClosedNotification } | { "method": "skills/changed", "params": SkillsChangedNotification } | { "method": "thread/name/updated", "params": ThreadNameUpdatedNotification } | { "method": "thread/goal/updated", "params": ThreadGoalUpdatedNotification } | { "method": "thread/goal/cleared", "params": ThreadGoalClearedNotification } | { "method": "thread/queue/changed", "params": ThreadQueueChangedNotification } | { "method": "thread/tokenUsage/updated", "params": ThreadTokenUsageUpdatedNotification } | { "method": "turn/started", "params": TurnStartedNotification } | { "method": "hook/started", "params": HookStartedNotification } | { "method": "turn/completed", "params": TurnCompletedNotification } | { "method": "hook/completed", "params": HookCompletedNotification } | { "method": "turn/diff/updated", "params": TurnDiffUpdatedNotification } | { "method": "turn/plan/updated", "params": TurnPlanUpdatedNotification } | { "method": "item/started", "params": ItemStartedNotification } | { "method": "item/autoApprovalReview/started", "params": ItemGuardianApprovalReviewStartedNotification } | { "method": "item/autoApprovalReview/completed", "params": ItemGuardianApprovalReviewCompletedNotification } | { "method": "item/completed", "params": ItemCompletedNotification } | { "method": "rawResponseItem/completed", "params": RawResponseItemCompletedNotification } | { "method": "item/agentMessage/delta", "params": AgentMessageDeltaNotification } | { "method": "item/plan/delta", "params": PlanDeltaNotification } | { "method": "command/exec/outputDelta", "params": CommandExecOutputDeltaNotification } | { "method": "process/outputDelta", "params": ProcessOutputDeltaNotification } | { "method": "process/exited", "params": ProcessExitedNotification } | { "method": "item/commandExecution/outputDelta", "params": CommandExecutionOutputDeltaNotification } | { "method": "item/commandExecution/terminalInteraction", "params": TerminalInteractionNotification } | { "method": "item/fileChange/outputDelta", "params": FileChangeOutputDeltaNotification } | { "method": "item/fileChange/patchUpdated", "params": FileChangePatchUpdatedNotification } | { "method": "serverRequest/resolved", "params": ServerRequestResolvedNotification } | { "method": "item/mcpToolCall/progress", "params": McpToolCallProgressNotification } | { "method": "mcpServer/oauthLogin/completed", "params": McpServerOauthLoginCompletedNotification } | { "method": "mcpServer/startupStatus/updated", "params": McpServerStatusUpdatedNotification } | { "method": "account/updated", "params": AccountUpdatedNotification } | { "method": "account/rateLimits/updated", "params": AccountRateLimitsUpdatedNotification } | { "method": "app/list/updated", "params": AppListUpdatedNotification } | { "method": "remoteControl/status/changed", "params": RemoteControlStatusChangedNotification } | { "method": "externalAgentConfig/import/completed", "params": ExternalAgentConfigImportCompletedNotification } | { "method": "fs/changed", "params": FsChangedNotification } | { "method": "item/reasoning/summaryTextDelta", "params": ReasoningSummaryTextDeltaNotification } | { "method": "item/reasoning/summaryPartAdded", "params": ReasoningSummaryPartAddedNotification } | { "method": "item/reasoning/textDelta", "params": ReasoningTextDeltaNotification } | { "method": "thread/compacted", "params": ContextCompactedNotification } | { "method": "model/rerouted", "params": ModelReroutedNotification } | { "method": "model/verification", "params": ModelVerificationNotification } | { "method": "warning", "params": WarningNotification } | { "method": "guardianWarning", "params": GuardianWarningNotification } | { "method": "deprecationNotice", "params": DeprecationNoticeNotification } | { "method": "configWarning", "params": ConfigWarningNotification } | { "method": "fuzzyFileSearch/sessionUpdated", "params": FuzzyFileSearchSessionUpdatedNotification } | { "method": "fuzzyFileSearch/sessionCompleted", "params": FuzzyFileSearchSessionCompletedNotification } | { "method": "thread/realtime/started", "params": ThreadRealtimeStartedNotification } | { "method": "thread/realtime/itemAdded", "params": ThreadRealtimeItemAddedNotification } | { "method": "thread/realtime/transcript/delta", "params": ThreadRealtimeTranscriptDeltaNotification } | { "method": "thread/realtime/transcript/done", "params": ThreadRealtimeTranscriptDoneNotification } | { "method": "thread/realtime/outputAudio/delta", "params": ThreadRealtimeOutputAudioDeltaNotification } | { "method": "thread/realtime/sdp", "params": ThreadRealtimeSdpNotification } | { "method": "thread/realtime/error", "params": ThreadRealtimeErrorNotification } | { "method": "thread/realtime/closed", "params": ThreadRealtimeClosedNotification } | { "method": "windows/worldWritableWarning", "params": WindowsWorldWritableWarningNotification } | { "method": "windowsSandbox/setupCompleted", "params": WindowsSandboxSetupCompletedNotification } | { "method": "account/login/completed", "params": AccountLoginCompletedNotification }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/QueuedTurn.ts b/codex-rs/app-server-protocol/schema/typescript/v2/QueuedTurn.ts new file mode 100644 index 0000000000..93f2703c0f --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/QueuedTurn.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { TurnStartParams } from "./TurnStartParams"; + +export type QueuedTurn = { id: string, turnStartParams: TurnStartParams, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadQueueAddParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadQueueAddParams.ts new file mode 100644 index 0000000000..1cd1518f2d --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadQueueAddParams.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { TurnStartParams } from "./TurnStartParams"; + +export type ThreadQueueAddParams = { threadId: string, turnStartParams: TurnStartParams, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadQueueAddResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadQueueAddResponse.ts new file mode 100644 index 0000000000..7fa9c79fa6 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadQueueAddResponse.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { QueuedTurn } from "./QueuedTurn"; + +export type ThreadQueueAddResponse = { queuedTurn: QueuedTurn, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadQueueChangedNotification.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadQueueChangedNotification.ts new file mode 100644 index 0000000000..d407a3abb3 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadQueueChangedNotification.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { QueuedTurn } from "./QueuedTurn"; + +export type ThreadQueueChangedNotification = { threadId: string, queuedTurns: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadQueueDeleteParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadQueueDeleteParams.ts new file mode 100644 index 0000000000..6c7f7bf031 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadQueueDeleteParams.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ThreadQueueDeleteParams = { threadId: string, queuedTurnId: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadQueueDeleteResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadQueueDeleteResponse.ts new file mode 100644 index 0000000000..49d0b639ad --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadQueueDeleteResponse.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ThreadQueueDeleteResponse = { deleted: boolean, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadQueueListParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadQueueListParams.ts new file mode 100644 index 0000000000..e3c4669449 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadQueueListParams.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ThreadQueueListParams = { threadId: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadQueueListResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadQueueListResponse.ts new file mode 100644 index 0000000000..0bf0e0a4ae --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadQueueListResponse.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { QueuedTurn } from "./QueuedTurn"; + +export type ThreadQueueListResponse = { queuedTurns: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadQueueReorderParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadQueueReorderParams.ts new file mode 100644 index 0000000000..471f077888 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadQueueReorderParams.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ThreadQueueReorderParams = { threadId: string, queuedTurnIds: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadQueueReorderResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadQueueReorderResponse.ts new file mode 100644 index 0000000000..77f1472d1a --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadQueueReorderResponse.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { QueuedTurn } from "./QueuedTurn"; + +export type ThreadQueueReorderResponse = { queuedTurns: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts index 950dd9839a..9be1d23441 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -301,6 +301,7 @@ export type { ProcessOutputDeltaNotification } from "./ProcessOutputDeltaNotific export type { ProcessOutputStream } from "./ProcessOutputStream"; export type { ProcessTerminalSize } from "./ProcessTerminalSize"; export type { ProfileV2 } from "./ProfileV2"; +export type { QueuedTurn } from "./QueuedTurn"; export type { RateLimitReachedType } from "./RateLimitReachedType"; export type { RateLimitSnapshot } from "./RateLimitSnapshot"; export type { RateLimitWindow } from "./RateLimitWindow"; @@ -372,6 +373,15 @@ export type { ThreadMetadataGitInfoUpdateParams } from "./ThreadMetadataGitInfoU export type { ThreadMetadataUpdateParams } from "./ThreadMetadataUpdateParams"; export type { ThreadMetadataUpdateResponse } from "./ThreadMetadataUpdateResponse"; export type { ThreadNameUpdatedNotification } from "./ThreadNameUpdatedNotification"; +export type { ThreadQueueAddParams } from "./ThreadQueueAddParams"; +export type { ThreadQueueAddResponse } from "./ThreadQueueAddResponse"; +export type { ThreadQueueChangedNotification } from "./ThreadQueueChangedNotification"; +export type { ThreadQueueDeleteParams } from "./ThreadQueueDeleteParams"; +export type { ThreadQueueDeleteResponse } from "./ThreadQueueDeleteResponse"; +export type { ThreadQueueListParams } from "./ThreadQueueListParams"; +export type { ThreadQueueListResponse } from "./ThreadQueueListResponse"; +export type { ThreadQueueReorderParams } from "./ThreadQueueReorderParams"; +export type { ThreadQueueReorderResponse } from "./ThreadQueueReorderResponse"; export type { ThreadReadParams } from "./ThreadReadParams"; export type { ThreadReadResponse } from "./ThreadReadResponse"; export type { ThreadRealtimeAudioChunk } from "./ThreadRealtimeAudioChunk"; diff --git a/codex-rs/app-server-protocol/src/export.rs b/codex-rs/app-server-protocol/src/export.rs index 0f9b33671b..d749a0332a 100644 --- a/codex-rs/app-server-protocol/src/export.rs +++ b/codex-rs/app-server-protocol/src/export.rs @@ -1291,6 +1291,12 @@ fn insert_definition( if existing == &schema { return Ok(()); } + if schemas_match_ignoring_root_metadata(existing, &schema) { + if existing.get("title").is_none() && schema.get("title").is_some() { + definitions.insert(name, schema); + } + return Ok(()); + } let existing_title = existing .get("title") @@ -1309,6 +1315,19 @@ fn insert_definition( Ok(()) } +fn schemas_match_ignoring_root_metadata(left: &Value, right: &Value) -> bool { + let (Value::Object(left), Value::Object(right)) = (left, right) else { + return false; + }; + let mut left = left.clone(); + let mut right = right.clone(); + left.remove("title"); + right.remove("title"); + left.remove("$schema"); + right.remove("$schema"); + left == right +} + fn write_json_schema_with_return(out_dir: &Path, name: &str) -> Result where T: JsonSchema, @@ -2099,6 +2118,26 @@ mod tests { use std::path::PathBuf; use uuid::Uuid; + #[test] + fn schema_match_ignores_top_level_metadata() { + let nested = serde_json::json!({ + "type": "object", + "properties": { + "threadId": { "type": "string" }, + }, + }); + let root = serde_json::json!({ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TurnStartParams", + "type": "object", + "properties": { + "threadId": { "type": "string" }, + }, + }); + + assert!(schemas_match_ignoring_root_metadata(&nested, &root)); + } + #[test] fn generated_ts_optional_nullable_fields_only_in_params() -> Result<()> { // Assert that "?: T | null" only appears in generated *Params types. diff --git a/codex-rs/app-server-protocol/src/protocol/common.rs b/codex-rs/app-server-protocol/src/protocol/common.rs index e72e3c14c7..d7548bff9b 100644 --- a/codex-rs/app-server-protocol/src/protocol/common.rs +++ b/codex-rs/app-server-protocol/src/protocol/common.rs @@ -511,6 +511,27 @@ client_request_definitions! { serialization: thread_id(params.thread_id), response: v2::ThreadGoalClearResponse, }, + ThreadQueueAdd => "thread/queue/add" { + params: v2::ThreadQueueAddParams, + inspect_params: true, + serialization: thread_id(params.thread_id), + response: v2::ThreadQueueAddResponse, + }, + ThreadQueueList => "thread/queue/list" { + params: v2::ThreadQueueListParams, + serialization: thread_id(params.thread_id), + response: v2::ThreadQueueListResponse, + }, + ThreadQueueDelete => "thread/queue/delete" { + params: v2::ThreadQueueDeleteParams, + serialization: thread_id(params.thread_id), + response: v2::ThreadQueueDeleteResponse, + }, + ThreadQueueReorder => "thread/queue/reorder" { + params: v2::ThreadQueueReorderParams, + serialization: thread_id(params.thread_id), + response: v2::ThreadQueueReorderResponse, + }, ThreadMetadataUpdate => "thread/metadata/update" { params: v2::ThreadMetadataUpdateParams, serialization: thread_id(params.thread_id), @@ -1423,6 +1444,7 @@ server_notification_definitions! { ThreadGoalUpdated => "thread/goal/updated" (v2::ThreadGoalUpdatedNotification), #[experimental("thread/goal/cleared")] ThreadGoalCleared => "thread/goal/cleared" (v2::ThreadGoalClearedNotification), + ThreadQueueChanged => "thread/queue/changed" (v2::ThreadQueueChangedNotification), ThreadTokenUsageUpdated => "thread/tokenUsage/updated" (v2::ThreadTokenUsageUpdatedNotification), TurnStarted => "turn/started" (v2::TurnStartedNotification), HookStarted => "hook/started" (v2::HookStartedNotification), diff --git a/codex-rs/app-server-protocol/src/protocol/v2/notification.rs b/codex-rs/app-server-protocol/src/protocol/v2/notification.rs index 8289cf5683..c0bed3cdad 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/notification.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/notification.rs @@ -1,3 +1,4 @@ +use super::QueuedTurn; use super::TurnError; use crate::RequestId; use schemars::JsonSchema; @@ -54,3 +55,11 @@ pub struct ServerRequestResolvedNotification { pub thread_id: String, pub request_id: RequestId, } + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct ThreadQueueChangedNotification { + pub thread_id: String, + pub queued_turns: Vec, +} diff --git a/codex-rs/app-server-protocol/src/protocol/v2/thread.rs b/codex-rs/app-server-protocol/src/protocol/v2/thread.rs index 458722b3a2..01ea0c3178 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/thread.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/thread.rs @@ -11,6 +11,7 @@ use super::ThreadSource; use super::Turn; use super::TurnEnvironmentParams; use super::TurnItemsView; +use super::TurnStartParams; use super::shared::v2_enum_from_core; use codex_experimental_api_macros::ExperimentalApi; use codex_protocol::config_types::Personality; @@ -635,6 +636,74 @@ pub struct ThreadGoalClearResponse { pub cleared: bool, } +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct QueuedTurn { + pub id: String, + pub turn_start_params: TurnStartParams, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct ThreadQueueAddParams { + pub thread_id: String, + #[experimental(nested)] + pub turn_start_params: TurnStartParams, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct ThreadQueueAddResponse { + pub queued_turn: QueuedTurn, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct ThreadQueueListParams { + pub thread_id: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct ThreadQueueListResponse { + pub queued_turns: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct ThreadQueueDeleteParams { + pub thread_id: String, + pub queued_turn_id: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct ThreadQueueDeleteResponse { + pub deleted: bool, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct ThreadQueueReorderParams { + pub thread_id: String, + pub queued_turn_ids: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct ThreadQueueReorderResponse { + pub queued_turns: Vec, +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 5dd75ee6f4..37e8158e22 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -159,6 +159,11 @@ Example with notification opt-out: - `thread/goal/clear` — clear the current persisted goal for a materialized thread; returns whether a goal was removed and emits `thread/goal/cleared` when state changes. - `thread/goal/updated` — notification emitted whenever a thread goal changes; includes the full current goal. - `thread/goal/cleared` — notification emitted whenever a thread goal is removed. +- `thread/queue/add` — append one future `turn/start` payload to a materialized thread queue and emit `thread/queue/changed`. +- `thread/queue/list` — read the current queued turns for a materialized thread. +- `thread/queue/delete` — remove one queued turn by id and emit `thread/queue/changed` when state changes. +- `thread/queue/reorder` — replace the queued-turn order with the supplied ids and emit `thread/queue/changed`. +- `thread/queue/changed` — notification emitted whenever the queued-turn list changes; includes the full ordered queue snapshot. - `thread/status/changed` — notification emitted when a loaded thread’s status changes (`threadId` + new `status`). - `thread/archive` — move a thread’s rollout file into the archived directory and attempt to move any spawned descendant thread rollout files; returns `{}` on success and emits `thread/archived` for each archived thread. - `thread/unsubscribe` — unsubscribe this connection from thread turn/item events. If this was the last subscriber, the server keeps the thread loaded and unloads it only after it has had no subscribers and no thread activity for 30 minutes, then emits `thread/closed`. @@ -563,6 +568,37 @@ Use `thread/goal/clear` to remove the current goal. { "method": "thread/goal/cleared", "params": { "threadId": "thr_123" } } ``` +### Example: Queue a follow-up turn + +Use `thread/queue/add` to persist a future turn for a materialized thread. App-server stores the same normalized inputs that `turn/start` accepts, starts the head queued turn after the active turn reaches a terminal state, and emits the full queue snapshot whenever the queue changes. + +```json +{ "method": "thread/queue/add", "id": 31, "params": { + "threadId": "thr_123", + "turnStartParams": { + "threadId": "thr_123", + "input": [{ "type": "text", "text": "Run the smoke tests", "textElements": [] }] + } +} } +{ "id": 31, "result": { "queuedTurn": { + "id": "queued_123", + "turnStartParams": { + "threadId": "thr_123", + "input": [{ "type": "text", "text": "Run the smoke tests", "textElements": [] }] + } +} } } +{ "method": "thread/queue/changed", "params": { + "threadId": "thr_123", + "queuedTurns": [{ + "id": "queued_123", + "turnStartParams": { + "threadId": "thr_123", + "input": [{ "type": "text", "text": "Run the smoke tests", "textElements": [] }] + } + }] +} } +``` + ### Example: Archive a thread Use `thread/archive` to move the persisted rollout (stored as a JSONL file on disk) into the archived sessions directory and attempt to move any spawned descendant thread rollouts. diff --git a/codex-rs/app-server/src/message_processor.rs b/codex-rs/app-server/src/message_processor.rs index 7006c40343..3a05eeb0c8 100644 --- a/codex-rs/app-server/src/message_processor.rs +++ b/codex-rs/app-server/src/message_processor.rs @@ -28,6 +28,7 @@ use crate::request_processors::PluginRequestProcessor; use crate::request_processors::ProcessExecRequestProcessor; use crate::request_processors::SearchRequestProcessor; use crate::request_processors::ThreadGoalRequestProcessor; +use crate::request_processors::ThreadQueueRequestProcessor; use crate::request_processors::ThreadRequestProcessor; use crate::request_processors::TurnRequestProcessor; use crate::request_processors::WindowsSandboxRequestProcessor; @@ -168,6 +169,7 @@ pub(crate) struct MessageProcessor { plugin_processor: PluginRequestProcessor, search_processor: SearchRequestProcessor, thread_goal_processor: ThreadGoalRequestProcessor, + thread_queue_processor: ThreadQueueRequestProcessor, thread_processor: ThreadRequestProcessor, turn_processor: TurnRequestProcessor, windows_sandbox_processor: WindowsSandboxRequestProcessor, @@ -375,6 +377,27 @@ impl MessageProcessor { thread_state_manager.clone(), state_db.clone(), ); + let turn_processor = TurnRequestProcessor::new( + auth_manager.clone(), + Arc::clone(&thread_manager), + outgoing.clone(), + analytics_events_client.clone(), + arg0_paths.clone(), + Arc::clone(&config), + config_manager.clone(), + Arc::clone(&pending_thread_unloads), + thread_state_manager.clone(), + thread_watch_manager.clone(), + Arc::clone(&thread_list_state_permit), + ); + let thread_queue_processor = ThreadQueueRequestProcessor::new( + Arc::clone(&thread_manager), + outgoing.clone(), + Arc::clone(&config), + thread_state_manager.clone(), + state_db.clone(), + turn_processor.clone(), + ); let thread_processor = ThreadRequestProcessor::new( auth_manager.clone(), Arc::clone(&thread_manager), @@ -384,24 +407,12 @@ impl MessageProcessor { config_manager.clone(), Arc::clone(&thread_store), Arc::clone(&pending_thread_unloads), - thread_state_manager.clone(), - thread_watch_manager.clone(), - Arc::clone(&thread_list_state_permit), - thread_goal_processor.clone(), - state_db, - ); - let turn_processor = TurnRequestProcessor::new( - auth_manager.clone(), - Arc::clone(&thread_manager), - outgoing.clone(), - analytics_events_client.clone(), - arg0_paths.clone(), - Arc::clone(&config), - config_manager.clone(), - pending_thread_unloads, thread_state_manager, thread_watch_manager, - thread_list_state_permit, + Arc::clone(&thread_list_state_permit), + thread_goal_processor.clone(), + thread_queue_processor.clone(), + state_db, ); if matches!(plugin_startup_tasks, crate::PluginStartupTasks::Start) { // Keep plugin startup warmups aligned at app-server startup. @@ -463,6 +474,7 @@ impl MessageProcessor { plugin_processor, search_processor, thread_goal_processor, + thread_queue_processor, thread_processor, turn_processor, windows_sandbox_processor, @@ -969,6 +981,22 @@ impl MessageProcessor { .thread_goal_clear(request_id.clone(), params) .await } + ClientRequest::ThreadQueueAdd { params, .. } => { + self.thread_queue_processor.thread_queue_add(params).await + } + ClientRequest::ThreadQueueList { params, .. } => { + self.thread_queue_processor.thread_queue_list(params).await + } + ClientRequest::ThreadQueueDelete { params, .. } => { + self.thread_queue_processor + .thread_queue_delete(params) + .await + } + ClientRequest::ThreadQueueReorder { params, .. } => { + self.thread_queue_processor + .thread_queue_reorder(params) + .await + } ClientRequest::ThreadMetadataUpdate { params, .. } => { self.thread_processor.thread_metadata_update(params).await } diff --git a/codex-rs/app-server/src/request_processors.rs b/codex-rs/app-server/src/request_processors.rs index 19201b8ae1..f923ef0e0b 100644 --- a/codex-rs/app-server/src/request_processors.rs +++ b/codex-rs/app-server/src/request_processors.rs @@ -465,6 +465,7 @@ pub(crate) use process_exec_processor::ProcessExecRequestProcessor; pub(crate) use search::SearchRequestProcessor; pub(crate) use thread_goal_processor::ThreadGoalRequestProcessor; pub(crate) use thread_processor::ThreadRequestProcessor; +pub(crate) use thread_queue_processor::ThreadQueueRequestProcessor; pub(crate) use turn_processor::TurnRequestProcessor; pub(crate) use windows_sandbox_processor::WindowsSandboxRequestProcessor; @@ -482,12 +483,14 @@ mod config_errors; mod request_errors; mod thread_goal_processor; mod thread_lifecycle; +mod thread_queue_processor; mod thread_summary; use self::config_errors::*; use self::request_errors::*; use self::thread_goal_processor::api_thread_goal_from_state; use self::thread_lifecycle::*; +use self::thread_queue_processor::send_thread_queue_snapshot_notification; use self::thread_summary::*; pub(crate) use self::thread_lifecycle::populate_thread_turns_from_history; diff --git a/codex-rs/app-server/src/request_processors/thread_lifecycle.rs b/codex-rs/app-server/src/request_processors/thread_lifecycle.rs index ef44a2b178..1a70a0e94c 100644 --- a/codex-rs/app-server/src/request_processors/thread_lifecycle.rs +++ b/codex-rs/app-server/src/request_processors/thread_lifecycle.rs @@ -1,4 +1,5 @@ use super::*; +use codex_app_server_protocol::ThreadQueueChangedNotification; pub(super) const THREAD_UNLOADING_DELAY: Duration = Duration::from_secs(30 * 60); @@ -12,6 +13,7 @@ pub(super) struct ListenerTaskContext { pub(super) thread_list_state_permit: Arc, pub(super) fallback_model_provider: String, pub(super) codex_home: PathBuf, + pub(super) thread_queue_processor: Option, } struct UnloadingState { @@ -242,6 +244,7 @@ pub(super) async fn ensure_listener_task_running( thread_list_state_permit, fallback_model_provider, codex_home, + thread_queue_processor, } = listener_task_context; let outgoing_for_task = Arc::clone(&outgoing); tokio::spawn(async move { @@ -320,6 +323,13 @@ pub(super) async fn ensure_listener_task_running( fallback_model_provider.clone(), ) .await; + if matches!(event.msg, EventMsg::TurnComplete(_) | EventMsg::TurnAborted(_)) + && let Some(thread_queue_processor) = thread_queue_processor.as_ref() + { + thread_queue_processor + .drain_thread_queue_after_terminal_turn(conversation_id) + .await; + } } unloading_watchers_open = unloading_state.wait_for_unloading_trigger() => { if !unloading_watchers_open { @@ -475,6 +485,19 @@ pub(super) async fn handle_thread_listener_command( ThreadListenerCommand::EmitThreadGoalSnapshot { state_db } => { send_thread_goal_snapshot_notification(outgoing, conversation_id, &state_db).await; } + ThreadListenerCommand::EmitThreadQueueChanged { queued_turns } => { + outgoing + .send_server_notification(ServerNotification::ThreadQueueChanged( + ThreadQueueChangedNotification { + thread_id: conversation_id.to_string(), + queued_turns, + }, + )) + .await; + } + ThreadListenerCommand::EmitThreadQueueSnapshot { state_db } => { + send_thread_queue_snapshot_notification(outgoing, conversation_id, &state_db).await; + } ThreadListenerCommand::ResolveServerRequest { request_id, completion_tx, @@ -643,6 +666,14 @@ pub(super) async fn handle_pending_thread_resume_request( ); } } + if let Some(state_db) = pending.thread_queue_state_db { + send_thread_queue_snapshot_notification(outgoing, conversation_id, &state_db).await; + } else { + tracing::warn!( + thread_id = %conversation_id, + "state db unavailable when reading thread queue for running thread resume" + ); + } outgoing .replay_requests_to_connection_for_thread(connection_id, conversation_id) .await; diff --git a/codex-rs/app-server/src/request_processors/thread_processor.rs b/codex-rs/app-server/src/request_processors/thread_processor.rs index 615e37f2c9..546cccd4ec 100644 --- a/codex-rs/app-server/src/request_processors/thread_processor.rs +++ b/codex-rs/app-server/src/request_processors/thread_processor.rs @@ -315,6 +315,7 @@ pub(crate) struct ThreadRequestProcessor { pub(super) thread_watch_manager: ThreadWatchManager, pub(super) thread_list_state_permit: Arc, pub(super) thread_goal_processor: ThreadGoalRequestProcessor, + pub(super) thread_queue_processor: ThreadQueueRequestProcessor, pub(super) state_db: Option, pub(super) background_tasks: TaskTracker, } @@ -334,6 +335,7 @@ impl ThreadRequestProcessor { thread_watch_manager: ThreadWatchManager, thread_list_state_permit: Arc, thread_goal_processor: ThreadGoalRequestProcessor, + thread_queue_processor: ThreadQueueRequestProcessor, state_db: Option, ) -> Self { Self { @@ -349,6 +351,7 @@ impl ThreadRequestProcessor { thread_watch_manager, thread_list_state_permit, thread_goal_processor, + thread_queue_processor, state_db, background_tasks: TaskTracker::new(), } @@ -752,6 +755,7 @@ impl ThreadRequestProcessor { thread_list_state_permit: self.thread_list_state_permit.clone(), fallback_model_provider: self.config.model_provider_id.clone(), codex_home: self.config.codex_home.to_path_buf(), + thread_queue_processor: Some(self.thread_queue_processor.clone()), } } @@ -849,6 +853,7 @@ impl ThreadRequestProcessor { thread_list_state_permit: self.thread_list_state_permit.clone(), fallback_model_provider: self.config.model_provider_id.clone(), codex_home: self.config.codex_home.to_path_buf(), + thread_queue_processor: Some(self.thread_queue_processor.clone()), }; let request_trace = request_context.request_trace(); let config_manager = self.config_manager.clone(); @@ -2561,6 +2566,12 @@ impl ThreadRequestProcessor { self.thread_goal_processor .emit_resume_goal_snapshot_and_continue(thread_id, codex_thread.as_ref()) .await; + self.thread_queue_processor + .emit_thread_queue_snapshot(thread_id) + .await; + self.thread_queue_processor + .drain_thread_queue_after_terminal_turn(thread_id) + .await; } Err(err) => { let error = internal_error(format!("error resuming thread: {err}")); @@ -2719,6 +2730,8 @@ impl ThreadRequestProcessor { .thread_goal_processor .pending_resume_goal_state(existing_thread.as_ref()) .await; + let thread_queue_state_db = + existing_thread.state_db().or_else(|| self.state_db.clone()); let command = crate::thread_state::ThreadListenerCommand::SendThreadResumeResponse( Box::new(crate::thread_state::PendingThreadResumeRequest { @@ -2729,6 +2742,7 @@ impl ThreadRequestProcessor { thread_summary, emit_thread_goal_update, thread_goal_state_db, + thread_queue_state_db, include_turns: !params.exclude_turns, }), ); diff --git a/codex-rs/app-server/src/request_processors/thread_queue_processor.rs b/codex-rs/app-server/src/request_processors/thread_queue_processor.rs new file mode 100644 index 0000000000..c229456e72 --- /dev/null +++ b/codex-rs/app-server/src/request_processors/thread_queue_processor.rs @@ -0,0 +1,347 @@ +use super::*; +use codex_app_server_protocol::QueuedTurn; +use codex_app_server_protocol::ServerNotification; +use codex_app_server_protocol::ThreadQueueAddParams; +use codex_app_server_protocol::ThreadQueueAddResponse; +use codex_app_server_protocol::ThreadQueueChangedNotification; +use codex_app_server_protocol::ThreadQueueDeleteParams; +use codex_app_server_protocol::ThreadQueueDeleteResponse; +use codex_app_server_protocol::ThreadQueueListParams; +use codex_app_server_protocol::ThreadQueueListResponse; +use codex_app_server_protocol::ThreadQueueReorderParams; +use codex_app_server_protocol::ThreadQueueReorderResponse; + +#[derive(Clone)] +pub(crate) struct ThreadQueueRequestProcessor { + thread_manager: Arc, + outgoing: Arc, + config: Arc, + thread_state_manager: ThreadStateManager, + state_db: Option, + turn_processor: TurnRequestProcessor, +} + +impl ThreadQueueRequestProcessor { + pub(crate) fn new( + thread_manager: Arc, + outgoing: Arc, + config: Arc, + thread_state_manager: ThreadStateManager, + state_db: Option, + turn_processor: TurnRequestProcessor, + ) -> Self { + Self { + thread_manager, + outgoing, + config, + thread_state_manager, + state_db, + turn_processor, + } + } + + pub(crate) async fn thread_queue_add( + &self, + params: ThreadQueueAddParams, + ) -> Result, JSONRPCErrorError> { + TurnRequestProcessor::validate_v2_input_limit(¶ms.turn_start_params.input)?; + let thread_id = parse_thread_id_for_request(params.thread_id.as_str())?; + if params.turn_start_params.thread_id != params.thread_id { + return Err(invalid_request( + "threadId must match turnStartParams.threadId".to_string(), + )); + } + let state_db = self.state_db_for_materialized_thread(thread_id).await?; + let turn_start_params_json = + serde_json::to_string(¶ms.turn_start_params).map_err(|err| { + internal_error(format!("failed to serialize queued turn params: {err}")) + })?; + let queued_turn = state_db + .append_thread_queued_turn(thread_id, turn_start_params_json) + .await + .map_err(|err| internal_error(format!("failed to add queued turn: {err}")))?; + let queued_turn = api_queued_turn_from_state(queued_turn) + .map_err(|err| internal_error(format!("failed to decode queued turn params: {err}")))?; + let queued_turns = read_api_thread_queue(&state_db, thread_id).await?; + self.emit_thread_queue_changed_ordered(thread_id, queued_turns) + .await; + self.drain_thread_queue_if_idle(thread_id, &state_db).await; + Ok(Some(ThreadQueueAddResponse { queued_turn }.into())) + } + + pub(crate) async fn thread_queue_list( + &self, + params: ThreadQueueListParams, + ) -> Result, JSONRPCErrorError> { + let thread_id = parse_thread_id_for_request(params.thread_id.as_str())?; + let state_db = self.state_db_for_materialized_thread(thread_id).await?; + let queued_turns = read_api_thread_queue(&state_db, thread_id).await?; + Ok(Some(ThreadQueueListResponse { queued_turns }.into())) + } + + pub(crate) async fn thread_queue_delete( + &self, + params: ThreadQueueDeleteParams, + ) -> Result, JSONRPCErrorError> { + let thread_id = parse_thread_id_for_request(params.thread_id.as_str())?; + let state_db = self.state_db_for_materialized_thread(thread_id).await?; + let deleted = state_db + .delete_thread_queued_turn(thread_id, params.queued_turn_id.as_str()) + .await + .map_err(|err| internal_error(format!("failed to delete queued turn: {err}")))?; + if deleted { + let queued_turns = read_api_thread_queue(&state_db, thread_id).await?; + self.emit_thread_queue_changed_ordered(thread_id, queued_turns) + .await; + } + Ok(Some(ThreadQueueDeleteResponse { deleted }.into())) + } + + pub(crate) async fn thread_queue_reorder( + &self, + params: ThreadQueueReorderParams, + ) -> Result, JSONRPCErrorError> { + let thread_id = parse_thread_id_for_request(params.thread_id.as_str())?; + let state_db = self.state_db_for_materialized_thread(thread_id).await?; + state_db + .reorder_thread_queued_turns(thread_id, params.queued_turn_ids.as_slice()) + .await + .map_err(|err| invalid_request(err.to_string()))?; + let queued_turns = read_api_thread_queue(&state_db, thread_id).await?; + self.emit_thread_queue_changed_ordered(thread_id, queued_turns.clone()) + .await; + Ok(Some(ThreadQueueReorderResponse { queued_turns }.into())) + } + + pub(crate) async fn drain_thread_queue_after_terminal_turn(&self, thread_id: ThreadId) { + let state_db = match self.state_db_for_materialized_thread(thread_id).await { + Ok(state_db) => state_db, + Err(err) => { + warn!( + "failed to open state db before draining thread queue for {thread_id}: {}", + err.message + ); + return; + } + }; + self.drain_thread_queue_if_idle(thread_id, &state_db).await; + } + + async fn drain_thread_queue_if_idle(&self, thread_id: ThreadId, state_db: &StateDbHandle) { + let Ok(thread) = self.thread_manager.get_thread(thread_id).await else { + return; + }; + if self + .thread_has_live_in_progress_turn(thread_id, thread.as_ref()) + .await + { + return; + } + let queued_turn = match state_db.first_thread_queued_turn(thread_id).await { + Ok(Some(queued_turn)) => queued_turn, + Ok(None) => return, + Err(err) => { + warn!("failed to read next queued turn for {thread_id}: {err}"); + return; + } + }; + let turn_start_params = + match serde_json::from_str(queued_turn.turn_start_params_json.as_str()) { + Ok(turn_start_params) => turn_start_params, + Err(err) => { + warn!("failed to decode next queued turn for {thread_id}: {err}"); + return; + } + }; + + match self + .turn_processor + .queued_turn_start(turn_start_params) + .await + { + Ok(_) => { + if let Err(err) = state_db + .delete_thread_queued_turn(thread_id, queued_turn.queued_turn_id.as_str()) + .await + { + warn!( + "failed to remove dispatched queued turn {} for {thread_id}: {err}", + queued_turn.queued_turn_id + ); + return; + } + } + Err(error) => { + warn!( + "failed to dispatch queued turn {} for {thread_id}: {}", + queued_turn.queued_turn_id, error.message + ); + return; + } + } + + match read_api_thread_queue(state_db, thread_id).await { + Ok(queued_turns) => { + self.emit_thread_queue_changed_ordered(thread_id, queued_turns) + .await; + } + Err(err) => warn!("{}", err.message), + } + } + + async fn thread_has_live_in_progress_turn( + &self, + thread_id: ThreadId, + thread: &CodexThread, + ) -> bool { + if matches!(thread.agent_status().await, AgentStatus::Running) { + return true; + } + let thread_state = self.thread_state_manager.thread_state(thread_id).await; + thread_state.lock().await.active_turn_snapshot().is_some() + } + + async fn state_db_for_materialized_thread( + &self, + thread_id: ThreadId, + ) -> Result { + if let Ok(thread) = self.thread_manager.get_thread(thread_id).await { + if thread.rollout_path().is_none() { + return Err(invalid_request(format!( + "ephemeral thread does not support persistent app-server state: {thread_id}" + ))); + } + if let Some(state_db) = thread.state_db() { + return Ok(state_db); + } + } else { + find_thread_path_by_id_str( + &self.config.codex_home, + &thread_id.to_string(), + self.state_db.as_deref(), + ) + .await + .map_err(|err| { + internal_error(format!("failed to locate thread id {thread_id}: {err}")) + })? + .ok_or_else(|| invalid_request(format!("thread not found: {thread_id}")))?; + } + + self.state_db.clone().ok_or_else(|| { + invalid_request(format!( + "thread does not support persistent app-server state: {thread_id}" + )) + }) + } + + pub(crate) async fn emit_thread_queue_snapshot(&self, thread_id: ThreadId) { + let state_db = match self.state_db_for_materialized_thread(thread_id).await { + Ok(state_db) => state_db, + Err(err) => { + warn!( + "failed to open state db before emitting thread queue resume snapshot for {thread_id}: {}", + err.message + ); + return; + } + }; + let listener_command_tx = { + let thread_state = self.thread_state_manager.thread_state(thread_id).await; + let thread_state = thread_state.lock().await; + thread_state.listener_command_tx() + }; + if let Some(listener_command_tx) = listener_command_tx { + let command = ThreadListenerCommand::EmitThreadQueueSnapshot { + state_db: state_db.clone(), + }; + if listener_command_tx.send(command).is_ok() { + return; + } + warn!( + "failed to enqueue thread queue snapshot for {thread_id}: listener command channel is closed" + ); + } + send_thread_queue_snapshot_notification(&self.outgoing, thread_id, &state_db).await; + } + + async fn emit_thread_queue_changed_ordered( + &self, + thread_id: ThreadId, + queued_turns: Vec, + ) { + let listener_command_tx = { + let thread_state = self.thread_state_manager.thread_state(thread_id).await; + let thread_state = thread_state.lock().await; + thread_state.listener_command_tx() + }; + if let Some(listener_command_tx) = listener_command_tx { + let command = ThreadListenerCommand::EmitThreadQueueChanged { + queued_turns: queued_turns.clone(), + }; + if listener_command_tx.send(command).is_ok() { + return; + } + warn!( + "failed to enqueue thread queue update for {thread_id}: listener command channel is closed" + ); + } + self.outgoing + .send_server_notification(ServerNotification::ThreadQueueChanged( + ThreadQueueChangedNotification { + thread_id: thread_id.to_string(), + queued_turns, + }, + )) + .await; + } +} + +fn parse_thread_id_for_request(thread_id: &str) -> Result { + ThreadId::from_string(thread_id) + .map_err(|err| invalid_request(format!("invalid thread id: {err}"))) +} + +fn api_queued_turn_from_state( + queued_turn: codex_state::ThreadQueuedTurn, +) -> anyhow::Result { + Ok(QueuedTurn { + id: queued_turn.queued_turn_id, + turn_start_params: serde_json::from_str(queued_turn.turn_start_params_json.as_str())?, + }) +} + +async fn read_api_thread_queue( + state_db: &StateDbHandle, + thread_id: ThreadId, +) -> Result, JSONRPCErrorError> { + state_db + .list_thread_queued_turns(thread_id) + .await + .map_err(|err| internal_error(format!("failed to read thread queue: {err}")))? + .into_iter() + .map(api_queued_turn_from_state) + .collect::, _>>() + .map_err(|err| internal_error(format!("failed to decode queued turn params: {err}"))) +} + +pub(super) async fn send_thread_queue_snapshot_notification( + outgoing: &Arc, + thread_id: ThreadId, + state_db: &StateDbHandle, +) { + match read_api_thread_queue(state_db, thread_id).await { + Ok(queued_turns) => { + outgoing + .send_server_notification(ServerNotification::ThreadQueueChanged( + ThreadQueueChangedNotification { + thread_id: thread_id.to_string(), + queued_turns, + }, + )) + .await; + } + Err(err) => { + warn!(thread_id = %thread_id, "{}", err.message); + } + } +} diff --git a/codex-rs/app-server/src/request_processors/turn_processor.rs b/codex-rs/app-server/src/request_processors/turn_processor.rs index bdc5847b0d..a2f02f1feb 100644 --- a/codex-rs/app-server/src/request_processors/turn_processor.rs +++ b/codex-rs/app-server/src/request_processors/turn_processor.rs @@ -15,6 +15,13 @@ pub(crate) struct TurnRequestProcessor { thread_list_state_permit: Arc, } +struct PreparedTurnStart { + thread_id: ThreadId, + thread: Arc, + turn_op: Op, + turn_has_input: bool, +} + impl TurnRequestProcessor { #[allow(clippy::too_many_arguments)] pub(crate) fn new( @@ -52,14 +59,30 @@ impl TurnRequestProcessor { app_server_client_name: Option, app_server_client_version: Option, ) -> Result, JSONRPCErrorError> { - self.turn_start_inner( - request_id, - params, - app_server_client_name, - app_server_client_version, - ) - .await - .map(|response| Some(response.into())) + if let Err(error) = Self::validate_v2_input_limit(¶ms.input) { + self.track_error_response( + &request_id, + &error, + Some(AnalyticsJsonRpcError::Input(InputError::TooLarge)), + ); + return Err(error); + } + let prepared = self + .prepare_turn_start(params, app_server_client_name, app_server_client_version) + .await + .inspect_err(|error| { + self.track_error_response(&request_id, error, /*error_type*/ None); + })?; + let response = self + .submit_prepared_turn_start(prepared, self.request_trace_context(&request_id).await) + .await + .inspect_err(|error| { + self.track_error_response(&request_id, error, /*error_type*/ None); + })?; + self.outgoing + .record_request_turn_id(&request_id, &response.turn.id) + .await; + Ok(Some(response.into())) } pub(crate) async fn thread_inject_items( @@ -71,6 +94,15 @@ impl TurnRequestProcessor { .map(|response| Some(response.into())) } + pub(crate) async fn queued_turn_start( + &self, + params: TurnStartParams, + ) -> Result { + Self::validate_v2_input_limit(¶ms.input)?; + let prepared = self.prepare_turn_start(params, None, None).await?; + self.submit_prepared_turn_start(prepared, None).await + } + pub(crate) async fn turn_steer( &self, request_id: &ConnectionRequestId, @@ -301,7 +333,7 @@ impl TurnRequestProcessor { error } - fn validate_v2_input_limit(items: &[V2UserInput]) -> Result<(), JSONRPCErrorError> { + pub(crate) fn validate_v2_input_limit(items: &[V2UserInput]) -> Result<(), JSONRPCErrorError> { let actual_chars: usize = items.iter().map(V2UserInput::text_char_count).sum(); if actual_chars > MAX_USER_INPUT_TEXT_CHARS { return Err(Self::input_too_large_error(actual_chars)); @@ -309,36 +341,19 @@ impl TurnRequestProcessor { Ok(()) } - async fn turn_start_inner( + async fn prepare_turn_start( &self, - request_id: ConnectionRequestId, params: TurnStartParams, app_server_client_name: Option, app_server_client_version: Option, - ) -> Result { - if let Err(error) = Self::validate_v2_input_limit(¶ms.input) { - self.track_error_response( - &request_id, - &error, - Some(AnalyticsJsonRpcError::Input(InputError::TooLarge)), - ); - return Err(error); - } - let (thread_id, thread) = - self.load_thread(¶ms.thread_id) - .await - .inspect_err(|error| { - self.track_error_response(&request_id, error, /*error_type*/ None); - })?; + ) -> Result { + let (thread_id, thread) = self.load_thread(¶ms.thread_id).await?; Self::set_app_server_client_info( thread.as_ref(), app_server_client_name, app_server_client_version, ) - .await - .inspect_err(|error| { - self.track_error_response(&request_id, error, /*error_type*/ None); - })?; + .await?; let collaboration_mode = params .collaboration_mode @@ -475,14 +490,29 @@ impl TurnRequestProcessor { responsesapi_client_metadata: params.responsesapi_client_metadata, } }; - let turn_id = self - .submit_core_op(&request_id, thread.as_ref(), turn_op) + Ok(PreparedTurnStart { + thread_id, + thread, + turn_op, + turn_has_input, + }) + } + + async fn submit_prepared_turn_start( + &self, + prepared: PreparedTurnStart, + trace_context: Option, + ) -> Result { + let PreparedTurnStart { + thread_id, + thread, + turn_op, + turn_has_input, + } = prepared; + let turn_id = thread + .submit_with_trace(turn_op, trace_context) .await - .map_err(|err| { - let error = internal_error(format!("failed to start turn: {err}")); - self.track_error_response(&request_id, &error, /*error_type*/ None); - error - })?; + .map_err(|err| internal_error(format!("failed to start turn: {err}")))?; if turn_has_input { let config_snapshot = thread.config_snapshot().await; @@ -496,9 +526,6 @@ impl TurnRequestProcessor { ); } - self.outgoing - .record_request_turn_id(&request_id, &turn_id) - .await; let turn = Turn { id: turn_id, items: vec![], @@ -1087,6 +1114,7 @@ impl TurnRequestProcessor { thread_list_state_permit: self.thread_list_state_permit.clone(), fallback_model_provider: self.config.model_provider_id.clone(), codex_home: self.config.codex_home.to_path_buf(), + thread_queue_processor: None, } } diff --git a/codex-rs/app-server/src/thread_state.rs b/codex-rs/app-server/src/thread_state.rs index dddbcf483b..66fdcaa9de 100644 --- a/codex-rs/app-server/src/thread_state.rs +++ b/codex-rs/app-server/src/thread_state.rs @@ -1,5 +1,6 @@ use crate::outgoing_message::ConnectionId; use crate::outgoing_message::ConnectionRequestId; +use codex_app_server_protocol::QueuedTurn; use codex_app_server_protocol::RequestId; use codex_app_server_protocol::ThreadGoal; use codex_app_server_protocol::ThreadHistoryBuilder; @@ -32,6 +33,7 @@ pub(crate) struct PendingThreadResumeRequest { pub(crate) thread_summary: codex_app_server_protocol::Thread, pub(crate) emit_thread_goal_update: bool, pub(crate) thread_goal_state_db: Option, + pub(crate) thread_queue_state_db: Option, pub(crate) include_turns: bool, } @@ -49,6 +51,14 @@ pub(crate) enum ThreadListenerCommand { EmitThreadGoalSnapshot { state_db: StateDbHandle, }, + // EmitThreadQueueChanged is used to order app-server queue updates with running-thread resume responses. + EmitThreadQueueChanged { + queued_turns: Vec, + }, + // EmitThreadQueueSnapshot is used to read and emit the latest queue state in listener order. + EmitThreadQueueSnapshot { + state_db: StateDbHandle, + }, // ResolveServerRequest is used to notify the client that the request has been resolved. // It is executed in the thread listener's context to ensure that the resolved notification is ordered with regard to the request itself. ResolveServerRequest { diff --git a/codex-rs/app-server/tests/suite/v2/mod.rs b/codex-rs/app-server/tests/suite/v2/mod.rs index 8e13df7825..f63ba9bf7f 100644 --- a/codex-rs/app-server/tests/suite/v2/mod.rs +++ b/codex-rs/app-server/tests/suite/v2/mod.rs @@ -52,6 +52,7 @@ mod thread_loaded_list; mod thread_memory_mode_set; mod thread_metadata_update; mod thread_name_websocket; +mod thread_queue; mod thread_read; mod thread_resume; mod thread_rollback; diff --git a/codex-rs/app-server/tests/suite/v2/thread_queue.rs b/codex-rs/app-server/tests/suite/v2/thread_queue.rs new file mode 100644 index 0000000000..e7ab34578b --- /dev/null +++ b/codex-rs/app-server/tests/suite/v2/thread_queue.rs @@ -0,0 +1,461 @@ +use anyhow::Result; +use app_test_support::McpProcess; +use app_test_support::create_final_assistant_message_sse_response; +use app_test_support::create_mock_responses_server_sequence_unchecked; +use app_test_support::create_shell_command_sse_response; +use app_test_support::to_response; +use codex_app_server_protocol::ItemStartedNotification; +use codex_app_server_protocol::JSONRPCResponse; +use codex_app_server_protocol::RequestId; +use codex_app_server_protocol::ThreadItem; +use codex_app_server_protocol::ThreadQueueAddResponse; +use codex_app_server_protocol::ThreadQueueDeleteResponse; +use codex_app_server_protocol::ThreadQueueListResponse; +use codex_app_server_protocol::ThreadQueueReorderResponse; +use codex_app_server_protocol::ThreadResumeParams; +use codex_app_server_protocol::ThreadResumeResponse; +use codex_app_server_protocol::ThreadStartParams; +use codex_app_server_protocol::ThreadStartResponse; +use codex_app_server_protocol::TurnStartParams; +use codex_app_server_protocol::TurnStartResponse; +use codex_app_server_protocol::UserInput; +use pretty_assertions::assert_eq; +use serde_json::json; +use tempfile::TempDir; +use tokio::time::timeout; + +#[cfg(windows)] +const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(25); +#[cfg(not(windows))] +const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); + +#[tokio::test] +async fn thread_queue_supports_add_list_reorder_and_delete() -> Result<()> { + let server = create_mock_responses_server_sequence_unchecked(vec![ + create_final_assistant_message_sse_response("materialized")?, + create_shell_command_sse_response( + sleep_command(/*seconds*/ 10), + /*workdir*/ None, + Some(10_000), + "keep-open", + )?, + ]) + .await; + let codex_home = TempDir::new()?; + create_config_toml(codex_home.path(), &server.uri())?; + + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; + let thread = start_materialized_thread(&mut mcp).await?; + let blocking_turn = start_blocking_turn(&mut mcp, thread.id.as_str()).await?; + + let first = add_queued_turn(&mut mcp, thread.id.as_str(), "first").await?; + let second = add_queued_turn(&mut mcp, thread.id.as_str(), "second").await?; + + let list_id = mcp + .send_raw_request( + "thread/queue/list", + Some(json!({ + "threadId": thread.id, + })), + ) + .await?; + let list_resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(list_id)), + ) + .await??; + let list: ThreadQueueListResponse = to_response(list_resp)?; + assert_eq!( + list.queued_turns + .iter() + .map(|turn| turn.id.as_str()) + .collect::>(), + vec![ + first.queued_turn.id.as_str(), + second.queued_turn.id.as_str() + ] + ); + + let reorder_id = mcp + .send_raw_request( + "thread/queue/reorder", + Some(json!({ + "threadId": thread.id, + "queuedTurnIds": [ + second.queued_turn.id, + first.queued_turn.id, + ], + })), + ) + .await?; + let reorder_resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(reorder_id)), + ) + .await??; + let reordered: ThreadQueueReorderResponse = to_response(reorder_resp)?; + assert_eq!( + reordered + .queued_turns + .iter() + .map(|turn| turn.id.as_str()) + .collect::>(), + vec![ + second.queued_turn.id.as_str(), + first.queued_turn.id.as_str() + ] + ); + + let delete_id = mcp + .send_raw_request( + "thread/queue/delete", + Some(json!({ + "threadId": thread.id, + "queuedTurnId": second.queued_turn.id, + })), + ) + .await?; + let delete_resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(delete_id)), + ) + .await??; + let deleted: ThreadQueueDeleteResponse = to_response(delete_resp)?; + assert!(deleted.deleted); + + mcp.interrupt_turn_and_wait_for_aborted(thread.id, blocking_turn.id, DEFAULT_READ_TIMEOUT) + .await?; + + Ok(()) +} + +#[tokio::test] +async fn thread_queue_drains_after_the_next_terminal_turn() -> Result<()> { + let server = create_mock_responses_server_sequence_unchecked(vec![ + create_final_assistant_message_sse_response("materialized")?, + create_shell_command_sse_response( + sleep_command(/*seconds*/ 1), + /*workdir*/ None, + Some(10_000), + "call1", + )?, + create_final_assistant_message_sse_response("manual")?, + create_final_assistant_message_sse_response("queued")?, + ]) + .await; + let codex_home = TempDir::new()?; + create_config_toml(codex_home.path(), &server.uri())?; + + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; + let thread = start_materialized_thread(&mut mcp).await?; + start_blocking_turn(&mut mcp, thread.id.as_str()).await?; + add_queued_turn(&mut mcp, thread.id.as_str(), "queued").await?; + timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_notification_message("turn/completed"), + ) + .await??; + timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_notification_message("turn/started"), + ) + .await??; + timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_notification_message("turn/completed"), + ) + .await??; + + let list_id = mcp + .send_raw_request( + "thread/queue/list", + Some(json!({ + "threadId": thread.id, + })), + ) + .await?; + let list_resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(list_id)), + ) + .await??; + let list: ThreadQueueListResponse = to_response(list_resp)?; + assert!(list.queued_turns.is_empty()); + + Ok(()) +} + +#[tokio::test] +async fn thread_queue_add_drains_immediately_when_the_thread_is_idle() -> Result<()> { + let server = create_mock_responses_server_sequence_unchecked(vec![ + create_final_assistant_message_sse_response("materialized")?, + create_final_assistant_message_sse_response("queued")?, + ]) + .await; + let codex_home = TempDir::new()?; + create_config_toml(codex_home.path(), &server.uri())?; + + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; + let thread = start_materialized_thread(&mut mcp).await?; + add_queued_turn(&mut mcp, thread.id.as_str(), "queued").await?; + + timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_notification_message("turn/started"), + ) + .await??; + timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_notification_message("turn/completed"), + ) + .await??; + + let list_id = mcp + .send_raw_request( + "thread/queue/list", + Some(json!({ + "threadId": thread.id, + })), + ) + .await?; + let list_resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(list_id)), + ) + .await??; + let list: ThreadQueueListResponse = to_response(list_resp)?; + assert!(list.queued_turns.is_empty()); + + Ok(()) +} + +#[tokio::test] +async fn thread_queue_drains_after_restart_and_resume() -> Result<()> { + let server = create_mock_responses_server_sequence_unchecked(vec![ + create_final_assistant_message_sse_response("materialized")?, + create_shell_command_sse_response( + sleep_command(/*seconds*/ 10), + /*workdir*/ None, + Some(10_000), + "keep-open", + )?, + create_final_assistant_message_sse_response("queued")?, + ]) + .await; + let codex_home = TempDir::new()?; + create_config_toml(codex_home.path(), &server.uri())?; + + let mut first_mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, first_mcp.initialize()).await??; + let thread = start_materialized_thread(&mut first_mcp).await?; + start_blocking_turn(&mut first_mcp, thread.id.as_str()).await?; + add_queued_turn(&mut first_mcp, thread.id.as_str(), "queued").await?; + drop(first_mcp); + + let mut second_mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, second_mcp.initialize()).await??; + let resume_id = second_mcp + .send_thread_resume_request(ThreadResumeParams { + thread_id: thread.id.clone(), + ..Default::default() + }) + .await?; + let resume_resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + second_mcp.read_stream_until_response_message(RequestId::Integer(resume_id)), + ) + .await??; + let ThreadResumeResponse { + thread: resumed, .. + } = to_response(resume_resp)?; + assert_eq!(resumed.id, thread.id); + + timeout( + DEFAULT_READ_TIMEOUT, + second_mcp.read_stream_until_notification_message("thread/queue/changed"), + ) + .await??; + timeout( + DEFAULT_READ_TIMEOUT, + second_mcp.read_stream_until_notification_message("turn/started"), + ) + .await??; + timeout( + DEFAULT_READ_TIMEOUT, + second_mcp.read_stream_until_notification_message("turn/completed"), + ) + .await??; + + let list_id = second_mcp + .send_raw_request( + "thread/queue/list", + Some(json!({ + "threadId": thread.id, + })), + ) + .await?; + let list_resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + second_mcp.read_stream_until_response_message(RequestId::Integer(list_id)), + ) + .await??; + let list: ThreadQueueListResponse = to_response(list_resp)?; + assert!(list.queued_turns.is_empty()); + + Ok(()) +} + +async fn start_materialized_thread( + mcp: &mut McpProcess, +) -> Result { + let start_id = mcp + .send_thread_start_request(ThreadStartParams { + model: Some("mock-model".to_string()), + ..Default::default() + }) + .await?; + let start_resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(start_id)), + ) + .await??; + let ThreadStartResponse { thread, .. } = to_response::(start_resp)?; + + let turn_id = mcp + .send_turn_start_request(turn_start_params(thread.id.as_str(), "materialize")) + .await?; + timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(turn_id)), + ) + .await??; + timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_notification_message("turn/started"), + ) + .await??; + timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_notification_message("turn/completed"), + ) + .await??; + + Ok(thread) +} + +async fn add_queued_turn( + mcp: &mut McpProcess, + thread_id: &str, + text: &str, +) -> Result { + let add_id = mcp + .send_raw_request( + "thread/queue/add", + Some(json!({ + "threadId": thread_id, + "turnStartParams": turn_start_params(thread_id, text), + })), + ) + .await?; + let add_resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(add_id)), + ) + .await??; + to_response(add_resp) +} + +async fn start_blocking_turn( + mcp: &mut McpProcess, + thread_id: &str, +) -> Result { + let turn_id = mcp + .send_turn_start_request(turn_start_params(thread_id, "manual")) + .await?; + let turn_resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(turn_id)), + ) + .await??; + let TurnStartResponse { turn } = to_response(turn_resp)?; + timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_notification_message("turn/started"), + ) + .await??; + wait_for_command_execution_started(mcp).await?; + Ok(turn) +} + +async fn wait_for_command_execution_started(mcp: &mut McpProcess) -> Result<()> { + loop { + let notif = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_notification_message("item/started"), + ) + .await??; + let started: ItemStartedNotification = serde_json::from_value( + notif + .params + .ok_or_else(|| anyhow::anyhow!("missing item/started params"))?, + )?; + if matches!(started.item, ThreadItem::CommandExecution { .. }) { + return Ok(()); + } + } +} + +fn sleep_command(seconds: u64) -> Vec { + #[cfg(target_os = "windows")] + { + vec![ + "powershell".to_string(), + "-Command".to_string(), + format!("Start-Sleep -Seconds {seconds}"), + ] + } + #[cfg(not(target_os = "windows"))] + { + vec!["sleep".to_string(), seconds.to_string()] + } +} + +fn turn_start_params(thread_id: &str, text: &str) -> TurnStartParams { + TurnStartParams { + thread_id: thread_id.to_string(), + input: vec![UserInput::Text { + text: text.to_string(), + text_elements: Vec::new(), + }], + ..Default::default() + } +} + +fn create_config_toml(codex_home: &std::path::Path, server_uri: &str) -> std::io::Result<()> { + let config_toml = codex_home.join("config.toml"); + std::fs::write( + config_toml, + format!( + r#" +model = "gpt-5.3-codex" +approval_policy = "never" +sandbox_mode = "read-only" + +model_provider = "mock_provider" + +[features] +personality = true + +[model_providers.mock_provider] +name = "Mock provider for test" +base_url = "{server_uri}/v1" +wire_api = "responses" +request_max_retries = 0 +stream_max_retries = 0 +"# + ), + ) +} diff --git a/codex-rs/state/migrations/0032_thread_queued_turns.sql b/codex-rs/state/migrations/0032_thread_queued_turns.sql new file mode 100644 index 0000000000..2d24ce18dc --- /dev/null +++ b/codex-rs/state/migrations/0032_thread_queued_turns.sql @@ -0,0 +1,7 @@ +CREATE TABLE thread_queued_turns ( + queued_turn_id TEXT PRIMARY KEY NOT NULL, + thread_id TEXT NOT NULL REFERENCES threads(id) ON DELETE CASCADE, + turn_start_params_json TEXT NOT NULL, + queue_order INTEGER NOT NULL, + UNIQUE(thread_id, queue_order) +); diff --git a/codex-rs/state/src/lib.rs b/codex-rs/state/src/lib.rs index 84582370a5..4672f79789 100644 --- a/codex-rs/state/src/lib.rs +++ b/codex-rs/state/src/lib.rs @@ -46,6 +46,7 @@ pub use model::ThreadGoal; pub use model::ThreadGoalStatus; pub use model::ThreadMetadata; pub use model::ThreadMetadataBuilder; +pub use model::ThreadQueuedTurn; pub use model::ThreadsPage; pub use runtime::RemoteControlEnrollmentRecord; pub use runtime::ThreadFilterOptions; diff --git a/codex-rs/state/src/model/mod.rs b/codex-rs/state/src/model/mod.rs index a431bc64c0..3ce25b59d5 100644 --- a/codex-rs/state/src/model/mod.rs +++ b/codex-rs/state/src/model/mod.rs @@ -5,6 +5,7 @@ mod log; mod memories; mod thread_goal; mod thread_metadata; +mod thread_queued_turn; pub use agent_job::AgentJob; pub use agent_job::AgentJobCreateParams; @@ -34,6 +35,7 @@ pub use thread_metadata::SortKey; pub use thread_metadata::ThreadMetadata; pub use thread_metadata::ThreadMetadataBuilder; pub use thread_metadata::ThreadsPage; +pub use thread_queued_turn::ThreadQueuedTurn; pub(crate) use agent_job::AgentJobItemRow; pub(crate) use agent_job::AgentJobRow; @@ -44,3 +46,4 @@ pub(crate) use thread_metadata::anchor_from_item; pub(crate) use thread_metadata::datetime_to_epoch_millis; pub(crate) use thread_metadata::datetime_to_epoch_seconds; pub(crate) use thread_metadata::epoch_millis_to_datetime; +pub(crate) use thread_queued_turn::ThreadQueuedTurnRow; diff --git a/codex-rs/state/src/model/thread_queued_turn.rs b/codex-rs/state/src/model/thread_queued_turn.rs new file mode 100644 index 0000000000..48719b11d1 --- /dev/null +++ b/codex-rs/state/src/model/thread_queued_turn.rs @@ -0,0 +1,43 @@ +use anyhow::Result; +use codex_protocol::ThreadId; +use sqlx::Row; +use sqlx::sqlite::SqliteRow; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ThreadQueuedTurn { + pub queued_turn_id: String, + pub thread_id: ThreadId, + pub turn_start_params_json: String, + pub queue_order: i64, +} + +pub(crate) struct ThreadQueuedTurnRow { + pub queued_turn_id: String, + pub thread_id: String, + pub turn_start_params_json: String, + pub queue_order: i64, +} + +impl ThreadQueuedTurnRow { + pub(crate) fn try_from_row(row: &SqliteRow) -> Result { + Ok(Self { + queued_turn_id: row.try_get("queued_turn_id")?, + thread_id: row.try_get("thread_id")?, + turn_start_params_json: row.try_get("turn_start_params_json")?, + queue_order: row.try_get("queue_order")?, + }) + } +} + +impl TryFrom for ThreadQueuedTurn { + type Error = anyhow::Error; + + fn try_from(row: ThreadQueuedTurnRow) -> Result { + Ok(Self { + queued_turn_id: row.queued_turn_id, + thread_id: ThreadId::try_from(row.thread_id)?, + turn_start_params_json: row.turn_start_params_json, + queue_order: row.queue_order, + }) + } +} diff --git a/codex-rs/state/src/runtime.rs b/codex-rs/state/src/runtime.rs index c8b4e7b98e..15f1f9d5e8 100644 --- a/codex-rs/state/src/runtime.rs +++ b/codex-rs/state/src/runtime.rs @@ -59,6 +59,7 @@ mod backfill; mod goals; mod logs; mod memories; +mod queued_turns; mod remote_control; #[cfg(test)] mod test_support; diff --git a/codex-rs/state/src/runtime/queued_turns.rs b/codex-rs/state/src/runtime/queued_turns.rs new file mode 100644 index 0000000000..888a14ebd8 --- /dev/null +++ b/codex-rs/state/src/runtime/queued_turns.rs @@ -0,0 +1,278 @@ +use super::*; +use uuid::Uuid; + +impl StateRuntime { + pub async fn list_thread_queued_turns( + &self, + thread_id: ThreadId, + ) -> anyhow::Result> { + let rows = sqlx::query( + r#" +SELECT + queued_turn_id, + thread_id, + turn_start_params_json, + queue_order +FROM thread_queued_turns +WHERE thread_id = ? +ORDER BY queue_order ASC + "#, + ) + .bind(thread_id.to_string()) + .fetch_all(self.pool.as_ref()) + .await?; + + rows.iter().map(thread_queued_turn_from_row).collect() + } + + pub async fn append_thread_queued_turn( + &self, + thread_id: ThreadId, + turn_start_params_json: String, + ) -> anyhow::Result { + let queued_turn_id = Uuid::new_v4().to_string(); + let row = sqlx::query( + r#" +INSERT INTO thread_queued_turns ( + queued_turn_id, + thread_id, + turn_start_params_json, + queue_order +) +VALUES ( + ?, + ?, + ?, + COALESCE((SELECT MAX(queue_order) + 1 FROM thread_queued_turns WHERE thread_id = ?), 0) +) +RETURNING + queued_turn_id, + thread_id, + turn_start_params_json, + queue_order + "#, + ) + .bind(queued_turn_id) + .bind(thread_id.to_string()) + .bind(turn_start_params_json) + .bind(thread_id.to_string()) + .fetch_one(self.pool.as_ref()) + .await?; + + thread_queued_turn_from_row(&row) + } + + pub async fn delete_thread_queued_turn( + &self, + thread_id: ThreadId, + queued_turn_id: &str, + ) -> anyhow::Result { + Ok(sqlx::query( + r#" +DELETE FROM thread_queued_turns +WHERE thread_id = ? AND queued_turn_id = ? + "#, + ) + .bind(thread_id.to_string()) + .bind(queued_turn_id) + .execute(self.pool.as_ref()) + .await? + .rows_affected() + > 0) + } + + pub async fn reorder_thread_queued_turns( + &self, + thread_id: ThreadId, + ordered_queued_turn_ids: &[String], + ) -> anyhow::Result<()> { + let current = self.list_thread_queued_turns(thread_id).await?; + let current_ids = current + .iter() + .map(|turn| turn.queued_turn_id.as_str()) + .collect::>(); + let next_ids = ordered_queued_turn_ids + .iter() + .map(String::as_str) + .collect::>(); + if current.len() != ordered_queued_turn_ids.len() || current_ids != next_ids { + anyhow::bail!("queued turn reorder ids must match the current queue"); + } + + let mut tx = self.pool.begin().await?; + for (queue_order, queued_turn_id) in ordered_queued_turn_ids.iter().enumerate() { + sqlx::query( + r#" +UPDATE thread_queued_turns +SET queue_order = ? +WHERE thread_id = ? AND queued_turn_id = ? + "#, + ) + .bind(-(queue_order as i64) - 1) + .bind(thread_id.to_string()) + .bind(queued_turn_id) + .execute(&mut *tx) + .await?; + } + for (queue_order, queued_turn_id) in ordered_queued_turn_ids.iter().enumerate() { + sqlx::query( + r#" +UPDATE thread_queued_turns +SET queue_order = ? +WHERE thread_id = ? AND queued_turn_id = ? + "#, + ) + .bind(queue_order as i64) + .bind(thread_id.to_string()) + .bind(queued_turn_id) + .execute(&mut *tx) + .await?; + } + tx.commit().await?; + Ok(()) + } + + pub async fn first_thread_queued_turn( + &self, + thread_id: ThreadId, + ) -> anyhow::Result> { + let row = sqlx::query( + r#" +SELECT + queued_turn_id, + thread_id, + turn_start_params_json, + queue_order +FROM thread_queued_turns +WHERE thread_id = ? +ORDER BY queue_order ASC +LIMIT 1 + "#, + ) + .bind(thread_id.to_string()) + .fetch_optional(self.pool.as_ref()) + .await?; + + row.map(|row| thread_queued_turn_from_row(&row)).transpose() + } +} + +fn thread_queued_turn_from_row( + row: &sqlx::sqlite::SqliteRow, +) -> anyhow::Result { + crate::model::ThreadQueuedTurnRow::try_from_row(row)?.try_into() +} + +#[cfg(test)] +mod tests { + use super::super::test_support::test_thread_metadata; + use super::super::test_support::unique_temp_dir; + use super::*; + use pretty_assertions::assert_eq; + + #[tokio::test] + async fn append_list_reorder_and_delete_thread_queued_turns() { + let codex_home = unique_temp_dir(); + let runtime = StateRuntime::init(codex_home.clone(), "mock".to_string()) + .await + .expect("state db should initialize"); + let thread_id = ThreadId::new(); + let metadata = test_thread_metadata(&codex_home, thread_id, codex_home.clone()); + runtime + .upsert_thread(&metadata) + .await + .expect("thread metadata insert should succeed"); + + let first = runtime + .append_thread_queued_turn(thread_id, "{\"first\":true}".to_string()) + .await + .expect("first queued turn insert should succeed"); + let second = runtime + .append_thread_queued_turn(thread_id, "{\"second\":true}".to_string()) + .await + .expect("second queued turn insert should succeed"); + assert_eq!( + runtime + .list_thread_queued_turns(thread_id) + .await + .expect("queued turn list should succeed"), + vec![first.clone(), second.clone()] + ); + + runtime + .reorder_thread_queued_turns( + thread_id, + &[second.queued_turn_id.clone(), first.queued_turn_id.clone()], + ) + .await + .expect("queued turn reorder should succeed"); + assert_eq!( + runtime + .list_thread_queued_turns(thread_id) + .await + .expect("queued turn list should succeed") + .into_iter() + .map(|turn| turn.queued_turn_id) + .collect::>(), + vec![second.queued_turn_id.clone(), first.queued_turn_id.clone()] + ); + + assert!( + runtime + .reorder_thread_queued_turns( + thread_id, + &[second.queued_turn_id.clone(), second.queued_turn_id.clone()], + ) + .await + .is_err() + ); + + assert!( + runtime + .delete_thread_queued_turn(thread_id, second.queued_turn_id.as_str()) + .await + .expect("queued turn delete should succeed") + ); + assert_eq!( + runtime + .list_thread_queued_turns(thread_id) + .await + .expect("queued turn list should succeed") + .into_iter() + .map(|turn| turn.queued_turn_id) + .collect::>(), + vec![first.queued_turn_id] + ); + } + + #[tokio::test] + async fn first_thread_queued_turn_reads_the_head() { + let codex_home = unique_temp_dir(); + let runtime = StateRuntime::init(codex_home.clone(), "mock".to_string()) + .await + .expect("state db should initialize"); + let thread_id = ThreadId::new(); + let metadata = test_thread_metadata(&codex_home, thread_id, codex_home.clone()); + runtime + .upsert_thread(&metadata) + .await + .expect("thread metadata insert should succeed"); + let queued_turn = runtime + .append_thread_queued_turn(thread_id, "{}".to_string()) + .await + .expect("queued turn insert should succeed"); + runtime + .append_thread_queued_turn(thread_id, "{\"later\":true}".to_string()) + .await + .expect("later queued turn insert should succeed"); + + assert_eq!( + runtime + .first_thread_queued_turn(thread_id) + .await + .expect("queued turn read should succeed") + .map(|turn| turn.queued_turn_id), + Some(queued_turn.queued_turn_id) + ); + } +} diff --git a/codex-rs/tui/src/app/app_server_event_targets.rs b/codex-rs/tui/src/app/app_server_event_targets.rs index 382a82a19f..cb0979611e 100644 --- a/codex-rs/tui/src/app/app_server_event_targets.rs +++ b/codex-rs/tui/src/app/app_server_event_targets.rs @@ -61,6 +61,9 @@ pub(super) fn server_notification_thread_target( ServerNotification::ThreadGoalCleared(notification) => { Some(notification.thread_id.as_str()) } + ServerNotification::ThreadQueueChanged(notification) => { + Some(notification.thread_id.as_str()) + } ServerNotification::TurnStarted(notification) => Some(notification.thread_id.as_str()), ServerNotification::HookStarted(notification) => Some(notification.thread_id.as_str()), ServerNotification::TurnCompleted(notification) => Some(notification.thread_id.as_str()), diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index adaba76b24..b1fdf5b54b 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -6430,6 +6430,7 @@ impl ChatWidget { | ServerNotification::ThreadStatusChanged(_) | ServerNotification::ThreadArchived(_) | ServerNotification::ThreadUnarchived(_) + | ServerNotification::ThreadQueueChanged(_) | ServerNotification::RawResponseItemCompleted(_) | ServerNotification::CommandExecOutputDelta(_) | ServerNotification::ProcessOutputDelta(_)