core-agent-ide/codex-rs/app-server-protocol/schema/json/EventMsg.json

9689 lines
245 KiB
JSON
Raw Normal View History

{
"$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"
},
"AgentMessageContent": {
"oneOf": [
{
"properties": {
"text": {
"type": "string"
},
"type": {
"enum": [
"Text"
],
"title": "TextAgentMessageContentType",
"type": "string"
}
},
"required": [
"text",
"type"
],
"title": "TextAgentMessageContent",
"type": "object"
}
]
},
"AgentStatus": {
"description": "Agent lifecycle status, derived from emitted events.",
"oneOf": [
{
"description": "Agent is waiting for initialization.",
"enum": [
"pending_init"
],
"type": "string"
},
{
"description": "Agent is currently running.",
"enum": [
"running"
],
"type": "string"
},
{
"additionalProperties": false,
"description": "Agent is done. Contains the final assistant message.",
"properties": {
"completed": {
"type": [
"string",
"null"
]
}
},
"required": [
"completed"
],
"title": "CompletedAgentStatus",
"type": "object"
},
{
"additionalProperties": false,
"description": "Agent encountered an error.",
"properties": {
"errored": {
"type": "string"
}
},
"required": [
"errored"
],
"title": "ErroredAgentStatus",
"type": "object"
},
{
"description": "Agent has been shutdown.",
"enum": [
"shutdown"
],
"type": "string"
},
{
"description": "Agent is not found.",
"enum": [
"not_found"
],
"type": "string"
}
]
},
"AskForApproval": {
"description": "Determines the conditions under which the user is consulted to approve running the command proposed by Codex.",
"oneOf": [
{
"description": "Under this policy, only \"known safe\" commands—as determined by `is_safe_command()`—that **only read files** are autoapproved. Everything else will ask the user to approve.",
"enum": [
"untrusted"
],
"type": "string"
},
{
"description": "DEPRECATED: *All* commands are autoapproved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox. Prefer `OnRequest` for interactive runs or `Never` for non-interactive runs.",
"enum": [
"on-failure"
],
"type": "string"
},
{
"description": "The model decides when to ask the user for approval.",
"enum": [
"on-request"
],
"type": "string"
},
feat: add Reject approval policy with granular prompt rejection controls (#12087) ## Why We need a way to auto-reject specific approval prompt categories without switching all approvals off. The goal is to let users independently control: - sandbox escalation approvals, - execpolicy `prompt` rule approvals, - MCP elicitation prompts. ## What changed - Added a new primary approval mode in `protocol/src/protocol.rs`: ```rust pub enum AskForApproval { // ... Reject(RejectConfig), // ... } pub struct RejectConfig { pub sandbox_approval: bool, pub rules: bool, pub mcp_elicitations: bool, } ``` - Wired `RejectConfig` semantics through approval paths in `core`: - `core/src/exec_policy.rs` - rejects rule-driven prompts when `rules = true` - rejects sandbox/escalation prompts when `sandbox_approval = true` - preserves rule priority when both rule and sandbox prompt conditions are present - `core/src/tools/sandboxing.rs` - applies `sandbox_approval` to default exec approval decisions and sandbox-failure retry gating - `core/src/safety.rs` - keeps `Reject { all false }` behavior aligned with `OnRequest` for patch safety - rejects out-of-root patch approvals when `sandbox_approval = true` - `core/src/mcp_connection_manager.rs` - auto-declines MCP elicitations when `mcp_elicitations = true` - Ensured approval policy used by MCP elicitation flow stays in sync with constrained session policy updates. - Updated app-server v2 conversions and generated schema/TypeScript artifacts for the new `Reject` shape. ## Verification Added focused unit coverage for the new behavior in: - `core/src/exec_policy.rs` - `core/src/tools/sandboxing.rs` - `core/src/mcp_connection_manager.rs` - `core/src/safety.rs` - `core/src/tools/runtimes/apply_patch.rs` Key cases covered include rule-vs-sandbox prompt precedence, MCP auto-decline behavior, and patch/sandbox retry behavior under `RejectConfig`.
2026-02-19 11:41:49 -08:00
{
"additionalProperties": false,
"description": "Fine-grained rejection controls for approval prompts.\n\nWhen a field is `true`, prompts of that category are automatically rejected instead of shown to the user.",
"properties": {
"reject": {
"$ref": "#/definitions/RejectConfig"
}
},
"required": [
"reject"
],
"title": "RejectAskForApproval",
"type": "object"
},
{
"description": "Never ask the user to approve commands. Failures are immediately returned to the model, and never escalated to the user for approval.",
"enum": [
"never"
],
"type": "string"
}
]
},
"ByteRange": {
"properties": {
"end": {
"description": "End byte offset (exclusive) within the UTF-8 text buffer.",
"format": "uint",
"minimum": 0.0,
"type": "integer"
},
"start": {
"description": "Start byte offset (inclusive) within the UTF-8 text buffer.",
"format": "uint",
"minimum": 0.0,
"type": "integer"
}
},
"required": [
"end",
"start"
],
"type": "object"
},
"CallToolResult": {
"description": "The server's response to a tool call.",
"properties": {
feat: replace custom mcp-types crate with equivalents from rmcp (#10349) We started working with MCP in Codex before https://crates.io/crates/rmcp was mature, so we had our own crate for MCP types that was generated from the MCP schema: https://github.com/openai/codex/blob/8b95d3e082376f4cb23e92641705a22afb28a9da/codex-rs/mcp-types/README.md Now that `rmcp` is more mature, it makes more sense to use their MCP types in Rust, as they handle details (like the `_meta` field) that our custom version ignored. Though one advantage that our custom types had is that our generated types implemented `JsonSchema` and `ts_rs::TS`, whereas the types in `rmcp` do not. As such, part of the work of this PR is leveraging the adapters between `rmcp` types and the serializable types that are API for us (app server and MCP) introduced in #10356. Note this PR results in a number of changes to `codex-rs/app-server-protocol/schema`, which merit special attention during review. We must ensure that these changes are still backwards-compatible, which is possible because we have: ```diff - export type CallToolResult = { content: Array<ContentBlock>, isError?: boolean, structuredContent?: JsonValue, }; + export type CallToolResult = { content: Array<JsonValue>, structuredContent?: JsonValue, isError?: boolean, _meta?: JsonValue, }; ``` so `ContentBlock` has been replaced with the more general `JsonValue`. Note that `ContentBlock` was defined as: ```typescript export type ContentBlock = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource; ``` so the deletion of those individual variants should not be a cause of great concern. Similarly, we have the following change in `codex-rs/app-server-protocol/schema/typescript/Tool.ts`: ``` - export type Tool = { annotations?: ToolAnnotations, description?: string, inputSchema: ToolInputSchema, name: string, outputSchema?: ToolOutputSchema, title?: string, }; + export type Tool = { name: string, title?: string, description?: string, inputSchema: JsonValue, outputSchema?: JsonValue, annotations?: JsonValue, icons?: Array<JsonValue>, _meta?: JsonValue, }; ``` so: - `annotations?: ToolAnnotations` ➡️ `JsonValue` - `inputSchema: ToolInputSchema` ➡️ `JsonValue` - `outputSchema?: ToolOutputSchema` ➡️ `JsonValue` and two new fields: `icons?: Array<JsonValue>, _meta?: JsonValue` --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/10349). * #10357 * __->__ #10349 * #10356
2026-02-02 17:41:55 -08:00
"_meta": true,
"content": {
feat: replace custom mcp-types crate with equivalents from rmcp (#10349) We started working with MCP in Codex before https://crates.io/crates/rmcp was mature, so we had our own crate for MCP types that was generated from the MCP schema: https://github.com/openai/codex/blob/8b95d3e082376f4cb23e92641705a22afb28a9da/codex-rs/mcp-types/README.md Now that `rmcp` is more mature, it makes more sense to use their MCP types in Rust, as they handle details (like the `_meta` field) that our custom version ignored. Though one advantage that our custom types had is that our generated types implemented `JsonSchema` and `ts_rs::TS`, whereas the types in `rmcp` do not. As such, part of the work of this PR is leveraging the adapters between `rmcp` types and the serializable types that are API for us (app server and MCP) introduced in #10356. Note this PR results in a number of changes to `codex-rs/app-server-protocol/schema`, which merit special attention during review. We must ensure that these changes are still backwards-compatible, which is possible because we have: ```diff - export type CallToolResult = { content: Array<ContentBlock>, isError?: boolean, structuredContent?: JsonValue, }; + export type CallToolResult = { content: Array<JsonValue>, structuredContent?: JsonValue, isError?: boolean, _meta?: JsonValue, }; ``` so `ContentBlock` has been replaced with the more general `JsonValue`. Note that `ContentBlock` was defined as: ```typescript export type ContentBlock = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource; ``` so the deletion of those individual variants should not be a cause of great concern. Similarly, we have the following change in `codex-rs/app-server-protocol/schema/typescript/Tool.ts`: ``` - export type Tool = { annotations?: ToolAnnotations, description?: string, inputSchema: ToolInputSchema, name: string, outputSchema?: ToolOutputSchema, title?: string, }; + export type Tool = { name: string, title?: string, description?: string, inputSchema: JsonValue, outputSchema?: JsonValue, annotations?: JsonValue, icons?: Array<JsonValue>, _meta?: JsonValue, }; ``` so: - `annotations?: ToolAnnotations` ➡️ `JsonValue` - `inputSchema: ToolInputSchema` ➡️ `JsonValue` - `outputSchema?: ToolOutputSchema` ➡️ `JsonValue` and two new fields: `icons?: Array<JsonValue>, _meta?: JsonValue` --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/10349). * #10357 * __->__ #10349 * #10356
2026-02-02 17:41:55 -08:00
"items": true,
"type": "array"
},
"isError": {
"type": [
"boolean",
"null"
]
},
"structuredContent": true
},
"required": [
"content"
],
"type": "object"
},
"CodexErrorInfo": {
"description": "Codex errors that we expose to clients.",
"oneOf": [
{
"enum": [
"context_window_exceeded",
"usage_limit_exceeded",
"server_overloaded",
"internal_server_error",
"unauthorized",
"bad_request",
"sandbox_error",
"thread_rollback_failed",
"other"
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"http_connection_failed": {
"properties": {
"http_status_code": {
"format": "uint16",
"minimum": 0.0,
"type": [
"integer",
"null"
]
}
},
"type": "object"
}
},
"required": [
"http_connection_failed"
],
"title": "HttpConnectionFailedCodexErrorInfo",
"type": "object"
},
{
"additionalProperties": false,
"description": "Failed to connect to the response SSE stream.",
"properties": {
"response_stream_connection_failed": {
"properties": {
"http_status_code": {
"format": "uint16",
"minimum": 0.0,
"type": [
"integer",
"null"
]
}
},
"type": "object"
}
},
"required": [
"response_stream_connection_failed"
],
"title": "ResponseStreamConnectionFailedCodexErrorInfo",
"type": "object"
},
{
"additionalProperties": false,
"description": "The response SSE stream disconnected in the middle of a turnbefore completion.",
"properties": {
"response_stream_disconnected": {
"properties": {
"http_status_code": {
"format": "uint16",
"minimum": 0.0,
"type": [
"integer",
"null"
]
}
},
"type": "object"
}
},
"required": [
"response_stream_disconnected"
],
"title": "ResponseStreamDisconnectedCodexErrorInfo",
"type": "object"
},
{
"additionalProperties": false,
"description": "Reached the retry limit for responses.",
"properties": {
"response_too_many_failed_attempts": {
"properties": {
"http_status_code": {
"format": "uint16",
"minimum": 0.0,
"type": [
"integer",
"null"
]
}
},
"type": "object"
}
},
"required": [
"response_too_many_failed_attempts"
],
"title": "ResponseTooManyFailedAttemptsCodexErrorInfo",
"type": "object"
}
]
},
"CollabAgentRef": {
"properties": {
"agent_nickname": {
"description": "Optional nickname assigned to an AgentControl-spawned sub-agent.",
"type": [
"string",
"null"
]
},
"agent_role": {
"description": "Optional role (agent_role) assigned to an AgentControl-spawned sub-agent.",
"type": [
"string",
"null"
]
},
"thread_id": {
"allOf": [
{
"$ref": "#/definitions/ThreadId"
}
],
"description": "Thread ID of the receiver/new agent."
}
},
"required": [
"thread_id"
],
"type": "object"
},
"CollabAgentStatusEntry": {
"properties": {
"agent_nickname": {
"description": "Optional nickname assigned to an AgentControl-spawned sub-agent.",
"type": [
"string",
"null"
]
},
"agent_role": {
"description": "Optional role (agent_role) assigned to an AgentControl-spawned sub-agent.",
"type": [
"string",
"null"
]
},
"status": {
"allOf": [
{
"$ref": "#/definitions/AgentStatus"
}
],
"description": "Last known status of the agent."
},
"thread_id": {
"allOf": [
{
"$ref": "#/definitions/ThreadId"
}
],
"description": "Thread ID of the receiver/new agent."
}
},
"required": [
"status",
"thread_id"
],
"type": "object"
},
"ContentItem": {
"oneOf": [
{
"properties": {
"text": {
"type": "string"
},
"type": {
"enum": [
"input_text"
],
"title": "InputTextContentItemType",
"type": "string"
}
},
"required": [
"text",
"type"
],
"title": "InputTextContentItem",
"type": "object"
},
{
"properties": {
"image_url": {
"type": "string"
},
"type": {
"enum": [
"input_image"
],
"title": "InputImageContentItemType",
"type": "string"
}
},
"required": [
"image_url",
"type"
],
"title": "InputImageContentItem",
"type": "object"
},
{
"properties": {
"text": {
"type": "string"
},
"type": {
"enum": [
"output_text"
],
"title": "OutputTextContentItemType",
"type": "string"
}
},
"required": [
"text",
"type"
],
"title": "OutputTextContentItem",
"type": "object"
}
]
},
"CreditsSnapshot": {
"properties": {
"balance": {
"type": [
"string",
"null"
]
},
"has_credits": {
"type": "boolean"
},
"unlimited": {
"type": "boolean"
}
},
"required": [
"has_credits",
"unlimited"
],
"type": "object"
},
"CustomPrompt": {
"properties": {
"argument_hint": {
"type": [
"string",
"null"
]
},
"content": {
"type": "string"
},
"description": {
"type": [
"string",
"null"
]
},
"name": {
"type": "string"
},
"path": {
"type": "string"
}
},
"required": [
"content",
"name",
"path"
],
"type": "object"
},
"Duration": {
"properties": {
"nanos": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"secs": {
"format": "uint64",
"minimum": 0.0,
"type": "integer"
}
},
"required": [
"nanos",
"secs"
],
"type": "object"
},
"DynamicToolCallOutputContentItem": {
"oneOf": [
{
"properties": {
"text": {
"type": "string"
},
"type": {
"enum": [
"inputText"
],
"title": "InputTextDynamicToolCallOutputContentItemType",
"type": "string"
}
},
"required": [
"text",
"type"
],
"title": "InputTextDynamicToolCallOutputContentItem",
"type": "object"
},
{
"properties": {
"imageUrl": {
"type": "string"
},
"type": {
"enum": [
"inputImage"
],
"title": "InputImageDynamicToolCallOutputContentItemType",
"type": "string"
}
},
"required": [
"imageUrl",
"type"
],
"title": "InputImageDynamicToolCallOutputContentItem",
"type": "object"
}
]
},
feat(app-server): support mcp elicitations in v2 api (#13425) This adds a first-class server request for MCP server elicitations: `mcpServer/elicitation/request`. Until now, MCP elicitation requests only showed up as a raw `codex/event/elicitation_request` event from core. That made it hard for v2 clients to handle elicitations using the same request/response flow as other server-driven interactions (like shell and `apply_patch` tools). This also updates the underlying MCP elicitation request handling in core to pass through the full MCP request (including URL and form data) so we can expose it properly in app-server. ### Why not `item/mcpToolCall/elicitationRequest`? This is because MCP elicitations are related to MCP servers first, and only optionally to a specific MCP tool call. In the MCP protocol, elicitation is a server-to-client capability: the server sends `elicitation/create`, and the client replies with an elicitation result. RMCP models it that way as well. In practice an elicitation is often triggered by an MCP tool call, but not always. ### What changed - add `mcpServer/elicitation/request` to the v2 app-server API - translate core `codex/event/elicitation_request` events into the new v2 server request - map client responses back into `Op::ResolveElicitation` so the MCP server can continue - update app-server docs and generated protocol schema - add an end-to-end app-server test that covers the full round trip through a real RMCP elicitation flow - The new test exercises a realistic case where an MCP tool call triggers an elicitation, the app-server emits mcpServer/elicitation/request, the client accepts it, and the tool call resumes and completes successfully. ### app-server API flow - Client starts a thread with `thread/start`. - Client starts a turn with `turn/start`. - App-server sends `item/started` for the `mcpToolCall`. - While that tool call is in progress, app-server sends `mcpServer/elicitation/request`. - Client responds to that request with `{ action: "accept" | "decline" | "cancel" }`. - App-server sends `serverRequest/resolved`. - App-server sends `item/completed` for the mcpToolCall. - App-server sends `turn/completed`. - If the turn is interrupted while the elicitation is pending, app-server still sends `serverRequest/resolved` before the turn finishes.
2026-03-05 07:20:20 -08:00
"ElicitationRequest": {
"oneOf": [
{
"properties": {
"_meta": true,
feat(app-server): support mcp elicitations in v2 api (#13425) This adds a first-class server request for MCP server elicitations: `mcpServer/elicitation/request`. Until now, MCP elicitation requests only showed up as a raw `codex/event/elicitation_request` event from core. That made it hard for v2 clients to handle elicitations using the same request/response flow as other server-driven interactions (like shell and `apply_patch` tools). This also updates the underlying MCP elicitation request handling in core to pass through the full MCP request (including URL and form data) so we can expose it properly in app-server. ### Why not `item/mcpToolCall/elicitationRequest`? This is because MCP elicitations are related to MCP servers first, and only optionally to a specific MCP tool call. In the MCP protocol, elicitation is a server-to-client capability: the server sends `elicitation/create`, and the client replies with an elicitation result. RMCP models it that way as well. In practice an elicitation is often triggered by an MCP tool call, but not always. ### What changed - add `mcpServer/elicitation/request` to the v2 app-server API - translate core `codex/event/elicitation_request` events into the new v2 server request - map client responses back into `Op::ResolveElicitation` so the MCP server can continue - update app-server docs and generated protocol schema - add an end-to-end app-server test that covers the full round trip through a real RMCP elicitation flow - The new test exercises a realistic case where an MCP tool call triggers an elicitation, the app-server emits mcpServer/elicitation/request, the client accepts it, and the tool call resumes and completes successfully. ### app-server API flow - Client starts a thread with `thread/start`. - Client starts a turn with `turn/start`. - App-server sends `item/started` for the `mcpToolCall`. - While that tool call is in progress, app-server sends `mcpServer/elicitation/request`. - Client responds to that request with `{ action: "accept" | "decline" | "cancel" }`. - App-server sends `serverRequest/resolved`. - App-server sends `item/completed` for the mcpToolCall. - App-server sends `turn/completed`. - If the turn is interrupted while the elicitation is pending, app-server still sends `serverRequest/resolved` before the turn finishes.
2026-03-05 07:20:20 -08:00
"message": {
"type": "string"
},
"mode": {
"enum": [
"form"
],
"type": "string"
},
"requested_schema": true
},
"required": [
"message",
"mode",
"requested_schema"
],
"type": "object"
},
{
"properties": {
"_meta": true,
feat(app-server): support mcp elicitations in v2 api (#13425) This adds a first-class server request for MCP server elicitations: `mcpServer/elicitation/request`. Until now, MCP elicitation requests only showed up as a raw `codex/event/elicitation_request` event from core. That made it hard for v2 clients to handle elicitations using the same request/response flow as other server-driven interactions (like shell and `apply_patch` tools). This also updates the underlying MCP elicitation request handling in core to pass through the full MCP request (including URL and form data) so we can expose it properly in app-server. ### Why not `item/mcpToolCall/elicitationRequest`? This is because MCP elicitations are related to MCP servers first, and only optionally to a specific MCP tool call. In the MCP protocol, elicitation is a server-to-client capability: the server sends `elicitation/create`, and the client replies with an elicitation result. RMCP models it that way as well. In practice an elicitation is often triggered by an MCP tool call, but not always. ### What changed - add `mcpServer/elicitation/request` to the v2 app-server API - translate core `codex/event/elicitation_request` events into the new v2 server request - map client responses back into `Op::ResolveElicitation` so the MCP server can continue - update app-server docs and generated protocol schema - add an end-to-end app-server test that covers the full round trip through a real RMCP elicitation flow - The new test exercises a realistic case where an MCP tool call triggers an elicitation, the app-server emits mcpServer/elicitation/request, the client accepts it, and the tool call resumes and completes successfully. ### app-server API flow - Client starts a thread with `thread/start`. - Client starts a turn with `turn/start`. - App-server sends `item/started` for the `mcpToolCall`. - While that tool call is in progress, app-server sends `mcpServer/elicitation/request`. - Client responds to that request with `{ action: "accept" | "decline" | "cancel" }`. - App-server sends `serverRequest/resolved`. - App-server sends `item/completed` for the mcpToolCall. - App-server sends `turn/completed`. - If the turn is interrupted while the elicitation is pending, app-server still sends `serverRequest/resolved` before the turn finishes.
2026-03-05 07:20:20 -08:00
"elicitation_id": {
"type": "string"
},
"message": {
"type": "string"
},
"mode": {
"enum": [
"url"
],
"type": "string"
},
"url": {
"type": "string"
}
},
"required": [
"elicitation_id",
"message",
"mode",
"url"
],
"type": "object"
}
]
},
"EventMsg": {
"description": "Response event from the agent NOTE: Make sure none of these values have optional types, as it will mess up the extension code-gen.",
"oneOf": [
{
"description": "Error while executing a submission",
"properties": {
"codex_error_info": {
"anyOf": [
{
"$ref": "#/definitions/CodexErrorInfo"
},
{
"type": "null"
}
],
"default": null
},
"message": {
"type": "string"
},
"type": {
"enum": [
"error"
],
"title": "ErrorEventMsgType",
"type": "string"
}
},
"required": [
"message",
"type"
],
"title": "ErrorEventMsg",
"type": "object"
},
{
"description": "Warning issued while processing a submission. Unlike `Error`, this indicates the turn continued but the user should still be notified.",
"properties": {
"message": {
"type": "string"
},
"type": {
"enum": [
"warning"
],
"title": "WarningEventMsgType",
"type": "string"
}
},
"required": [
"message",
"type"
],
"title": "WarningEventMsg",
"type": "object"
},
{
"description": "Realtime conversation lifecycle start event.",
"properties": {
"session_id": {
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"realtime_conversation_started"
],
"title": "RealtimeConversationStartedEventMsgType",
"type": "string"
}
},
"required": [
"type"
],
"title": "RealtimeConversationStartedEventMsg",
"type": "object"
},
{
"description": "Realtime conversation streaming payload event.",
"properties": {
"payload": {
"$ref": "#/definitions/RealtimeEvent"
},
"type": {
"enum": [
"realtime_conversation_realtime"
],
"title": "RealtimeConversationRealtimeEventMsgType",
"type": "string"
}
},
"required": [
"payload",
"type"
],
"title": "RealtimeConversationRealtimeEventMsg",
"type": "object"
},
{
"description": "Realtime conversation lifecycle close event.",
"properties": {
"reason": {
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"realtime_conversation_closed"
],
"title": "RealtimeConversationClosedEventMsgType",
"type": "string"
}
},
"required": [
"type"
],
"title": "RealtimeConversationClosedEventMsg",
"type": "object"
},
{
"description": "Model routing changed from the requested model to a different model.",
"properties": {
"from_model": {
"type": "string"
},
"reason": {
"$ref": "#/definitions/ModelRerouteReason"
},
"to_model": {
"type": "string"
},
"type": {
"enum": [
"model_reroute"
],
"title": "ModelRerouteEventMsgType",
"type": "string"
}
},
"required": [
"from_model",
"reason",
"to_model",
"type"
],
"title": "ModelRerouteEventMsg",
"type": "object"
},
{
"description": "Conversation history was compacted (either automatically or manually).",
"properties": {
"type": {
"enum": [
"context_compacted"
],
"title": "ContextCompactedEventMsgType",
"type": "string"
}
},
"required": [
"type"
],
"title": "ContextCompactedEventMsg",
"type": "object"
},
{
"description": "Conversation history was rolled back by dropping the last N user turns.",
"properties": {
"num_turns": {
"description": "Number of user turns that were removed from context.",
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"type": {
"enum": [
"thread_rolled_back"
],
"title": "ThreadRolledBackEventMsgType",
"type": "string"
}
},
"required": [
"num_turns",
"type"
],
"title": "ThreadRolledBackEventMsg",
"type": "object"
},
{
"description": "Agent has started a turn. v1 wire format uses `task_started`; accept `turn_started` for v2 interop.",
"properties": {
"collaboration_mode_kind": {
"allOf": [
{
"$ref": "#/definitions/ModeKind"
}
],
Cleanup collaboration mode variants (#10404) ## Summary This PR simplifies collaboration modes to the visible set `default | plan`, while preserving backward compatibility for older partners that may still send legacy mode names. Specifically: - Renames the old Code behavior to **Default**. - Keeps **Plan** as-is. - Removes **Custom** mode behavior (fallbacks now resolve to Default). - Keeps `PairProgramming` and `Execute` internally for compatibility plumbing, while removing them from schema/API and UI visibility. - Adds legacy input aliasing so older clients can still send old mode names. ## What Changed 1. Mode enum and compatibility - `ModeKind` now uses `Plan` + `Default` as active/public modes. - `ModeKind::Default` deserialization accepts legacy values: - `code` - `pair_programming` - `execute` - `custom` - `PairProgramming` and `Execute` variants remain in code but are hidden from protocol/schema generation. - `Custom` variant is removed; previous custom fallbacks now map to `Default`. 2. Collaboration presets and templates - Built-in presets now return only: - `Plan` - `Default` - Template rename: - `core/templates/collaboration_mode/code.md` -> `default.md` - `execute.md` and `pair_programming.md` remain on disk but are not surfaced in visible preset lists. 3. TUI updates - Updated user-facing naming and prompts from “Code” to “Default”. - Updated mode-cycle and indicator behavior to reflect only visible `Plan` and `Default`. - Updated corresponding tests and snapshots. 4. request_user_input behavior - `request_user_input` remains allowed only in `Plan` mode. - Rejection messaging now consistently treats non-plan modes as `Default`. 5. Schemas - Regenerated config and app-server schemas. - Public schema types now advertise mode values as: - `plan` - `default` ## Backward Compatibility Notes - Incoming legacy mode names (`code`, `pair_programming`, `execute`, `custom`) are accepted and coerced to `default`. - Outgoing/public schema surfaces intentionally expose only `plan | default`. - This allows tolerant ingestion of older partner payloads while standardizing new integrations on the reduced mode set. ## Codex author `codex fork 019c1fae-693b-7840-b16e-9ad38ea0bd00`
2026-02-03 09:23:53 -08:00
"default": "default"
},
"model_context_window": {
"format": "int64",
"type": [
"integer",
"null"
]
},
chore: persist turn_id in rollout session and make turn_id uuid based (#11246) Problem: 1. turn id is constructed in-memory; 2. on resuming threads, turn_id might not be unique; 3. client cannot no the boundary of a turn from rollout files easily. This PR does three things: 1. persist `task_started` and `task_complete` events; 1. persist `turn_id` in rollout turn events; 5. generate turn_id as unique uuids instead of incrementing it in memory. This helps us resolve the issue of clients wanting to have unique turn ids for resuming a thread, and knowing the boundry of each turn in rollout files. example debug logs ``` 2026-02-11T00:32:10.746876Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=8 turn=Turn { id: "019c4a07-d809-74c3-bc4b-fd9618487b4b", items: [UserMessage { id: "item-24", content: [Text { text: "hi", text_elements: [] }] }, AgentMessage { id: "item-25", text: "Hi. I’m in the workspace with your current changes loaded and ready. Send the next task and I’ll execute it end-to-end." }], status: Completed, error: None } 2026-02-11T00:32:10.746888Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=9 turn=Turn { id: "019c4a18-1004-76c0-a0fb-a77610f6a9b8", items: [UserMessage { id: "item-26", content: [Text { text: "hello", text_elements: [] }] }, AgentMessage { id: "item-27", text: "Hello. Ready for the next change in `codex-rs`; I can continue from the current in-progress diff or start a new task." }], status: Completed, error: None } 2026-02-11T00:32:10.746899Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=10 turn=Turn { id: "019c4a19-41f0-7db0-ad78-74f1503baeb8", items: [UserMessage { id: "item-28", content: [Text { text: "hello", text_elements: [] }] }, AgentMessage { id: "item-29", text: "Hello. Send the specific change you want in `codex-rs`, and I’ll implement it and run the required checks." }], status: Completed, error: None } ``` backward compatibility: if you try to resume an old session without task_started and task_complete event populated, the following happens: - If you resume and do nothing: those reconstructed historical IDs can differ next time you resume. - If you resume and send a new turn: the new turn gets a fresh UUID from live submission flow and is persisted, so that new turn’s ID is stable on later resumes. I think this behavior is fine, because we only care about deterministic turn id once a turn is triggered.
2026-02-10 19:56:01 -08:00
"turn_id": {
"type": "string"
},
"type": {
"enum": [
"task_started"
],
"title": "TaskStartedEventMsgType",
"type": "string"
}
},
"required": [
chore: persist turn_id in rollout session and make turn_id uuid based (#11246) Problem: 1. turn id is constructed in-memory; 2. on resuming threads, turn_id might not be unique; 3. client cannot no the boundary of a turn from rollout files easily. This PR does three things: 1. persist `task_started` and `task_complete` events; 1. persist `turn_id` in rollout turn events; 5. generate turn_id as unique uuids instead of incrementing it in memory. This helps us resolve the issue of clients wanting to have unique turn ids for resuming a thread, and knowing the boundry of each turn in rollout files. example debug logs ``` 2026-02-11T00:32:10.746876Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=8 turn=Turn { id: "019c4a07-d809-74c3-bc4b-fd9618487b4b", items: [UserMessage { id: "item-24", content: [Text { text: "hi", text_elements: [] }] }, AgentMessage { id: "item-25", text: "Hi. I’m in the workspace with your current changes loaded and ready. Send the next task and I’ll execute it end-to-end." }], status: Completed, error: None } 2026-02-11T00:32:10.746888Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=9 turn=Turn { id: "019c4a18-1004-76c0-a0fb-a77610f6a9b8", items: [UserMessage { id: "item-26", content: [Text { text: "hello", text_elements: [] }] }, AgentMessage { id: "item-27", text: "Hello. Ready for the next change in `codex-rs`; I can continue from the current in-progress diff or start a new task." }], status: Completed, error: None } 2026-02-11T00:32:10.746899Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=10 turn=Turn { id: "019c4a19-41f0-7db0-ad78-74f1503baeb8", items: [UserMessage { id: "item-28", content: [Text { text: "hello", text_elements: [] }] }, AgentMessage { id: "item-29", text: "Hello. Send the specific change you want in `codex-rs`, and I’ll implement it and run the required checks." }], status: Completed, error: None } ``` backward compatibility: if you try to resume an old session without task_started and task_complete event populated, the following happens: - If you resume and do nothing: those reconstructed historical IDs can differ next time you resume. - If you resume and send a new turn: the new turn gets a fresh UUID from live submission flow and is persisted, so that new turn’s ID is stable on later resumes. I think this behavior is fine, because we only care about deterministic turn id once a turn is triggered.
2026-02-10 19:56:01 -08:00
"turn_id",
"type"
],
"title": "TaskStartedEventMsg",
"type": "object"
},
{
"description": "Agent has completed all actions. v1 wire format uses `task_complete`; accept `turn_complete` for v2 interop.",
"properties": {
"last_agent_message": {
"type": [
"string",
"null"
]
},
chore: persist turn_id in rollout session and make turn_id uuid based (#11246) Problem: 1. turn id is constructed in-memory; 2. on resuming threads, turn_id might not be unique; 3. client cannot no the boundary of a turn from rollout files easily. This PR does three things: 1. persist `task_started` and `task_complete` events; 1. persist `turn_id` in rollout turn events; 5. generate turn_id as unique uuids instead of incrementing it in memory. This helps us resolve the issue of clients wanting to have unique turn ids for resuming a thread, and knowing the boundry of each turn in rollout files. example debug logs ``` 2026-02-11T00:32:10.746876Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=8 turn=Turn { id: "019c4a07-d809-74c3-bc4b-fd9618487b4b", items: [UserMessage { id: "item-24", content: [Text { text: "hi", text_elements: [] }] }, AgentMessage { id: "item-25", text: "Hi. I’m in the workspace with your current changes loaded and ready. Send the next task and I’ll execute it end-to-end." }], status: Completed, error: None } 2026-02-11T00:32:10.746888Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=9 turn=Turn { id: "019c4a18-1004-76c0-a0fb-a77610f6a9b8", items: [UserMessage { id: "item-26", content: [Text { text: "hello", text_elements: [] }] }, AgentMessage { id: "item-27", text: "Hello. Ready for the next change in `codex-rs`; I can continue from the current in-progress diff or start a new task." }], status: Completed, error: None } 2026-02-11T00:32:10.746899Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=10 turn=Turn { id: "019c4a19-41f0-7db0-ad78-74f1503baeb8", items: [UserMessage { id: "item-28", content: [Text { text: "hello", text_elements: [] }] }, AgentMessage { id: "item-29", text: "Hello. Send the specific change you want in `codex-rs`, and I’ll implement it and run the required checks." }], status: Completed, error: None } ``` backward compatibility: if you try to resume an old session without task_started and task_complete event populated, the following happens: - If you resume and do nothing: those reconstructed historical IDs can differ next time you resume. - If you resume and send a new turn: the new turn gets a fresh UUID from live submission flow and is persisted, so that new turn’s ID is stable on later resumes. I think this behavior is fine, because we only care about deterministic turn id once a turn is triggered.
2026-02-10 19:56:01 -08:00
"turn_id": {
"type": "string"
},
"type": {
"enum": [
"task_complete"
],
"title": "TaskCompleteEventMsgType",
"type": "string"
}
},
"required": [
chore: persist turn_id in rollout session and make turn_id uuid based (#11246) Problem: 1. turn id is constructed in-memory; 2. on resuming threads, turn_id might not be unique; 3. client cannot no the boundary of a turn from rollout files easily. This PR does three things: 1. persist `task_started` and `task_complete` events; 1. persist `turn_id` in rollout turn events; 5. generate turn_id as unique uuids instead of incrementing it in memory. This helps us resolve the issue of clients wanting to have unique turn ids for resuming a thread, and knowing the boundry of each turn in rollout files. example debug logs ``` 2026-02-11T00:32:10.746876Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=8 turn=Turn { id: "019c4a07-d809-74c3-bc4b-fd9618487b4b", items: [UserMessage { id: "item-24", content: [Text { text: "hi", text_elements: [] }] }, AgentMessage { id: "item-25", text: "Hi. I’m in the workspace with your current changes loaded and ready. Send the next task and I’ll execute it end-to-end." }], status: Completed, error: None } 2026-02-11T00:32:10.746888Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=9 turn=Turn { id: "019c4a18-1004-76c0-a0fb-a77610f6a9b8", items: [UserMessage { id: "item-26", content: [Text { text: "hello", text_elements: [] }] }, AgentMessage { id: "item-27", text: "Hello. Ready for the next change in `codex-rs`; I can continue from the current in-progress diff or start a new task." }], status: Completed, error: None } 2026-02-11T00:32:10.746899Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=10 turn=Turn { id: "019c4a19-41f0-7db0-ad78-74f1503baeb8", items: [UserMessage { id: "item-28", content: [Text { text: "hello", text_elements: [] }] }, AgentMessage { id: "item-29", text: "Hello. Send the specific change you want in `codex-rs`, and I’ll implement it and run the required checks." }], status: Completed, error: None } ``` backward compatibility: if you try to resume an old session without task_started and task_complete event populated, the following happens: - If you resume and do nothing: those reconstructed historical IDs can differ next time you resume. - If you resume and send a new turn: the new turn gets a fresh UUID from live submission flow and is persisted, so that new turn’s ID is stable on later resumes. I think this behavior is fine, because we only care about deterministic turn id once a turn is triggered.
2026-02-10 19:56:01 -08:00
"turn_id",
"type"
],
"title": "TaskCompleteEventMsg",
"type": "object"
},
{
"description": "Usage update for the current session, including totals and last turn. Optional means unknown — UIs should not display when `None`.",
"properties": {
"info": {
"anyOf": [
{
"$ref": "#/definitions/TokenUsageInfo"
},
{
"type": "null"
}
]
},
"rate_limits": {
"anyOf": [
{
"$ref": "#/definitions/RateLimitSnapshot"
},
{
"type": "null"
}
]
},
"type": {
"enum": [
"token_count"
],
"title": "TokenCountEventMsgType",
"type": "string"
}
},
"required": [
"type"
],
"title": "TokenCountEventMsg",
"type": "object"
},
{
"description": "Agent text output message",
"properties": {
"message": {
"type": "string"
},
"phase": {
"anyOf": [
{
"$ref": "#/definitions/MessagePhase"
},
{
"type": "null"
}
],
"default": null
},
"type": {
"enum": [
"agent_message"
],
"title": "AgentMessageEventMsgType",
"type": "string"
}
},
"required": [
"message",
"type"
],
"title": "AgentMessageEventMsg",
"type": "object"
},
{
"description": "User/system input message (what was sent to the model)",
"properties": {
"images": {
"description": "Image URLs sourced from `UserInput::Image`. These are safe to replay in legacy UI history events and correspond to images sent to the model.",
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
},
"local_images": {
"default": [],
"description": "Local file paths sourced from `UserInput::LocalImage`. These are kept so the UI can reattach images when editing history, and should not be sent to the model or treated as API-ready URLs.",
"items": {
"type": "string"
},
"type": "array"
},
"message": {
"type": "string"
},
"text_elements": {
"default": [],
"description": "UI-defined spans within `message` used to render or persist special elements.",
"items": {
"$ref": "#/definitions/TextElement"
},
"type": "array"
},
"type": {
"enum": [
"user_message"
],
"title": "UserMessageEventMsgType",
"type": "string"
}
},
"required": [
"message",
"type"
],
"title": "UserMessageEventMsg",
"type": "object"
},
{
"description": "Agent text output delta message",
"properties": {
"delta": {
"type": "string"
},
"type": {
"enum": [
"agent_message_delta"
],
"title": "AgentMessageDeltaEventMsgType",
"type": "string"
}
},
"required": [
"delta",
"type"
],
"title": "AgentMessageDeltaEventMsg",
"type": "object"
},
{
"description": "Reasoning event from agent.",
"properties": {
"text": {
"type": "string"
},
"type": {
"enum": [
"agent_reasoning"
],
"title": "AgentReasoningEventMsgType",
"type": "string"
}
},
"required": [
"text",
"type"
],
"title": "AgentReasoningEventMsg",
"type": "object"
},
{
"description": "Agent reasoning delta event from agent.",
"properties": {
"delta": {
"type": "string"
},
"type": {
"enum": [
"agent_reasoning_delta"
],
"title": "AgentReasoningDeltaEventMsgType",
"type": "string"
}
},
"required": [
"delta",
"type"
],
"title": "AgentReasoningDeltaEventMsg",
"type": "object"
},
{
"description": "Raw chain-of-thought from agent.",
"properties": {
"text": {
"type": "string"
},
"type": {
"enum": [
"agent_reasoning_raw_content"
],
"title": "AgentReasoningRawContentEventMsgType",
"type": "string"
}
},
"required": [
"text",
"type"
],
"title": "AgentReasoningRawContentEventMsg",
"type": "object"
},
{
"description": "Agent reasoning content delta event from agent.",
"properties": {
"delta": {
"type": "string"
},
"type": {
"enum": [
"agent_reasoning_raw_content_delta"
],
"title": "AgentReasoningRawContentDeltaEventMsgType",
"type": "string"
}
},
"required": [
"delta",
"type"
],
"title": "AgentReasoningRawContentDeltaEventMsg",
"type": "object"
},
{
"description": "Signaled when the model begins a new reasoning summary section (e.g., a new titled block).",
"properties": {
"item_id": {
"default": "",
"type": "string"
},
"summary_index": {
"default": 0,
"format": "int64",
"type": "integer"
},
"type": {
"enum": [
"agent_reasoning_section_break"
],
"title": "AgentReasoningSectionBreakEventMsgType",
"type": "string"
}
},
"required": [
"type"
],
"title": "AgentReasoningSectionBreakEventMsg",
"type": "object"
},
{
"description": "Ack the client's configure message.",
"properties": {
"approval_policy": {
"allOf": [
{
"$ref": "#/definitions/AskForApproval"
}
],
"description": "When to escalate for approval for execution"
},
"cwd": {
"description": "Working directory that should be treated as the *root* of the session.",
"type": "string"
},
"forked_from_id": {
"anyOf": [
{
"$ref": "#/definitions/ThreadId"
},
{
"type": "null"
}
]
},
"history_entry_count": {
"description": "Current number of entries in the history log.",
"format": "uint",
"minimum": 0.0,
"type": "integer"
},
"history_log_id": {
"description": "Identifier of the history log file (inode on Unix, 0 otherwise).",
"format": "uint64",
"minimum": 0.0,
"type": "integer"
},
"initial_messages": {
"description": "Optional initial messages (as events) for resumed sessions. When present, UIs can use these to seed the history.",
"items": {
"$ref": "#/definitions/EventMsg"
},
"type": [
"array",
"null"
]
},
"model": {
"description": "Tell the client what model is being queried.",
"type": "string"
},
"model_provider_id": {
"type": "string"
},
"network_proxy": {
"anyOf": [
{
"$ref": "#/definitions/SessionNetworkProxyRuntime"
},
{
"type": "null"
}
],
"description": "Runtime proxy bind addresses, when the managed proxy was started for this session."
},
"reasoning_effort": {
"anyOf": [
{
"$ref": "#/definitions/ReasoningEffort"
},
{
"type": "null"
}
],
"description": "The effort the model is putting into reasoning about the user's request."
},
"rollout_path": {
"description": "Path in which the rollout is stored. Can be `None` for ephemeral threads",
"type": [
"string",
"null"
]
},
"sandbox_policy": {
"allOf": [
{
"$ref": "#/definitions/SandboxPolicy"
}
],
"description": "How to sandbox commands executed in the system"
},
"service_tier": {
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
]
},
"session_id": {
"$ref": "#/definitions/ThreadId"
},
"thread_name": {
"description": "Optional user-facing thread name (may be unset).",
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"session_configured"
],
"title": "SessionConfiguredEventMsgType",
"type": "string"
}
},
"required": [
"approval_policy",
"cwd",
"history_entry_count",
"history_log_id",
"model",
"model_provider_id",
"sandbox_policy",
"session_id",
"type"
],
"title": "SessionConfiguredEventMsg",
"type": "object"
},
{
"description": "Updated session metadata (e.g., thread name changes).",
"properties": {
"thread_id": {
"$ref": "#/definitions/ThreadId"
},
"thread_name": {
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"thread_name_updated"
],
"title": "ThreadNameUpdatedEventMsgType",
"type": "string"
}
},
"required": [
"thread_id",
"type"
],
"title": "ThreadNameUpdatedEventMsg",
"type": "object"
},
{
"description": "Incremental MCP startup progress updates.",
"properties": {
"server": {
"description": "Server name being started.",
"type": "string"
},
"status": {
"allOf": [
{
"$ref": "#/definitions/McpStartupStatus"
}
],
"description": "Current startup status."
},
"type": {
"enum": [
"mcp_startup_update"
],
"title": "McpStartupUpdateEventMsgType",
"type": "string"
}
},
"required": [
"server",
"status",
"type"
],
"title": "McpStartupUpdateEventMsg",
"type": "object"
},
{
"description": "Aggregate MCP startup completion summary.",
"properties": {
"cancelled": {
"items": {
"type": "string"
},
"type": "array"
},
"failed": {
"items": {
"$ref": "#/definitions/McpStartupFailure"
},
"type": "array"
},
"ready": {
"items": {
"type": "string"
},
"type": "array"
},
"type": {
"enum": [
"mcp_startup_complete"
],
"title": "McpStartupCompleteEventMsgType",
"type": "string"
}
},
"required": [
"cancelled",
"failed",
"ready",
"type"
],
"title": "McpStartupCompleteEventMsg",
"type": "object"
},
{
"properties": {
"call_id": {
"description": "Identifier so this can be paired with the McpToolCallEnd event.",
"type": "string"
},
"invocation": {
"$ref": "#/definitions/McpInvocation"
},
"type": {
"enum": [
"mcp_tool_call_begin"
],
"title": "McpToolCallBeginEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"invocation",
"type"
],
"title": "McpToolCallBeginEventMsg",
"type": "object"
},
{
"properties": {
"call_id": {
"description": "Identifier for the corresponding McpToolCallBegin that finished.",
"type": "string"
},
"duration": {
"$ref": "#/definitions/Duration"
},
"invocation": {
"$ref": "#/definitions/McpInvocation"
},
"result": {
"allOf": [
{
"$ref": "#/definitions/Result_of_CallToolResult_or_String"
}
],
"description": "Result of the tool call. Note this could be an error."
},
"type": {
"enum": [
"mcp_tool_call_end"
],
"title": "McpToolCallEndEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"duration",
"invocation",
"result",
"type"
],
"title": "McpToolCallEndEventMsg",
"type": "object"
},
{
"properties": {
"call_id": {
"type": "string"
},
"type": {
"enum": [
"web_search_begin"
],
"title": "WebSearchBeginEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"type"
],
"title": "WebSearchBeginEventMsg",
"type": "object"
},
{
"properties": {
"action": {
"$ref": "#/definitions/ResponsesApiWebSearchAction"
},
"call_id": {
"type": "string"
},
"query": {
"type": "string"
},
"type": {
"enum": [
"web_search_end"
],
"title": "WebSearchEndEventMsgType",
"type": "string"
}
},
"required": [
"action",
"call_id",
"query",
"type"
],
"title": "WebSearchEndEventMsg",
"type": "object"
},
{
"properties": {
"call_id": {
"type": "string"
},
"type": {
"enum": [
"image_generation_begin"
],
"title": "ImageGenerationBeginEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"type"
],
"title": "ImageGenerationBeginEventMsg",
"type": "object"
},
{
"properties": {
"call_id": {
"type": "string"
},
"result": {
"type": "string"
},
"revised_prompt": {
"type": [
"string",
"null"
]
},
"saved_path": {
"type": [
"string",
"null"
]
},
"status": {
"type": "string"
},
"type": {
"enum": [
"image_generation_end"
],
"title": "ImageGenerationEndEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"result",
"status",
"type"
],
"title": "ImageGenerationEndEventMsg",
"type": "object"
},
{
"description": "Notification that the server is about to execute a command.",
"properties": {
"call_id": {
"description": "Identifier so this can be paired with the ExecCommandEnd event.",
"type": "string"
},
"command": {
"description": "The command to be executed.",
"items": {
"type": "string"
},
"type": "array"
},
"cwd": {
"description": "The command's working directory if not the default cwd for the agent.",
"type": "string"
},
"interaction_input": {
"description": "Raw input sent to a unified exec session (if this is an interaction event).",
"type": [
"string",
"null"
]
},
"parsed_cmd": {
"items": {
"$ref": "#/definitions/ParsedCommand"
},
"type": "array"
},
"process_id": {
"description": "Identifier for the underlying PTY process (when available).",
"type": [
"string",
"null"
]
},
"source": {
"allOf": [
{
"$ref": "#/definitions/ExecCommandSource"
}
],
"default": "agent",
"description": "Where the command originated. Defaults to Agent for backward compatibility."
},
"turn_id": {
"description": "Turn ID that this command belongs to.",
"type": "string"
},
"type": {
"enum": [
"exec_command_begin"
],
"title": "ExecCommandBeginEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"command",
"cwd",
"parsed_cmd",
"turn_id",
"type"
],
"title": "ExecCommandBeginEventMsg",
"type": "object"
},
{
"description": "Incremental chunk of output from a running command.",
"properties": {
"call_id": {
"description": "Identifier for the ExecCommandBegin that produced this chunk.",
"type": "string"
},
"chunk": {
"description": "Raw bytes from the stream (may not be valid UTF-8).",
"type": "string"
},
"stream": {
"allOf": [
{
"$ref": "#/definitions/ExecOutputStream"
}
],
"description": "Which stream produced this chunk."
},
"type": {
"enum": [
"exec_command_output_delta"
],
"title": "ExecCommandOutputDeltaEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"chunk",
"stream",
"type"
],
"title": "ExecCommandOutputDeltaEventMsg",
"type": "object"
},
{
"description": "Terminal interaction for an in-progress command (stdin sent and stdout observed).",
"properties": {
"call_id": {
"description": "Identifier for the ExecCommandBegin that produced this chunk.",
"type": "string"
},
"process_id": {
"description": "Process id associated with the running command.",
"type": "string"
},
"stdin": {
"description": "Stdin sent to the running session.",
"type": "string"
},
"type": {
"enum": [
"terminal_interaction"
],
"title": "TerminalInteractionEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"process_id",
"stdin",
"type"
],
"title": "TerminalInteractionEventMsg",
"type": "object"
},
{
"properties": {
"aggregated_output": {
"default": "",
"description": "Captured aggregated output",
"type": "string"
},
"call_id": {
"description": "Identifier for the ExecCommandBegin that finished.",
"type": "string"
},
"command": {
"description": "The command that was executed.",
"items": {
"type": "string"
},
"type": "array"
},
"cwd": {
"description": "The command's working directory if not the default cwd for the agent.",
"type": "string"
},
"duration": {
"allOf": [
{
"$ref": "#/definitions/Duration"
}
],
"description": "The duration of the command execution."
},
"exit_code": {
"description": "The command's exit code.",
"format": "int32",
"type": "integer"
},
"formatted_output": {
"description": "Formatted output from the command, as seen by the model.",
"type": "string"
},
"interaction_input": {
"description": "Raw input sent to a unified exec session (if this is an interaction event).",
"type": [
"string",
"null"
]
},
"parsed_cmd": {
"items": {
"$ref": "#/definitions/ParsedCommand"
},
"type": "array"
},
"process_id": {
"description": "Identifier for the underlying PTY process (when available).",
"type": [
"string",
"null"
]
},
"source": {
"allOf": [
{
"$ref": "#/definitions/ExecCommandSource"
}
],
"default": "agent",
"description": "Where the command originated. Defaults to Agent for backward compatibility."
},
feat(app-server): experimental flag to persist extended history (#11227) This PR adds an experimental `persist_extended_history` bool flag to app-server thread APIs so rollout logs can retain a richer set of EventMsgs for non-lossy Thread > Turn > ThreadItems reconstruction (i.e. on `thread/resume`). ### Motivation Today, our rollout recorder only persists a small subset (e.g. user message, reasoning, assistant message) of `EventMsg` types, dropping a good number (like command exec, file change, etc.) that are important for reconstructing full item history for `thread/resume`, `thread/read`, and `thread/fork`. Some clients want to be able to resume a thread without lossiness. This lossiness is primarily a UI thing, since what the model sees are `ResponseItem` and not `EventMsg`. ### Approach This change introduces an opt-in `persist_full_history` flag to preserve those events when you start/resume/fork a thread (defaults to `false`). This is done by adding an `EventPersistenceMode` to the rollout recorder: - `Limited` (existing behavior, default) - `Extended` (new opt-in behavior) In `Extended` mode, persist additional `EventMsg` variants needed for non-lossy app-server `ThreadItem` reconstruction. We now store the following ThreadItems that we didn't before: - web search - command execution - patch/file changes - MCP tool calls - image view calls - collab tool outcomes - context compaction - review mode enter/exit For **command executions** in particular, we truncate the output using the existing `truncate_text` from core to store an upper bound of 10,000 bytes, which is also the default value for truncating tool outputs shown to the model. This keeps the size of the rollout file and command execution items returned over the wire reasonable. And we also persist `EventMsg::Error` which we can now map back to the Turn's status and populates the Turn's error metadata. #### Updates to EventMsgs To truly make `thread/resume` non-lossy, we also needed to persist the `status` on `EventMsg::CommandExecutionEndEvent` and `EventMsg::PatchApplyEndEvent`. Previously it was not obvious whether a command failed or was declined (similar for apply_patch). These EventMsgs were never persisted before so I made it a required field.
2026-02-12 11:34:22 -08:00
"status": {
"allOf": [
{
"$ref": "#/definitions/ExecCommandStatus"
}
],
"description": "Completion status for this command execution."
},
"stderr": {
"description": "Captured stderr",
"type": "string"
},
"stdout": {
"description": "Captured stdout",
"type": "string"
},
"turn_id": {
"description": "Turn ID that this command belongs to.",
"type": "string"
},
"type": {
"enum": [
"exec_command_end"
],
"title": "ExecCommandEndEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"command",
"cwd",
"duration",
"exit_code",
"formatted_output",
"parsed_cmd",
feat(app-server): experimental flag to persist extended history (#11227) This PR adds an experimental `persist_extended_history` bool flag to app-server thread APIs so rollout logs can retain a richer set of EventMsgs for non-lossy Thread > Turn > ThreadItems reconstruction (i.e. on `thread/resume`). ### Motivation Today, our rollout recorder only persists a small subset (e.g. user message, reasoning, assistant message) of `EventMsg` types, dropping a good number (like command exec, file change, etc.) that are important for reconstructing full item history for `thread/resume`, `thread/read`, and `thread/fork`. Some clients want to be able to resume a thread without lossiness. This lossiness is primarily a UI thing, since what the model sees are `ResponseItem` and not `EventMsg`. ### Approach This change introduces an opt-in `persist_full_history` flag to preserve those events when you start/resume/fork a thread (defaults to `false`). This is done by adding an `EventPersistenceMode` to the rollout recorder: - `Limited` (existing behavior, default) - `Extended` (new opt-in behavior) In `Extended` mode, persist additional `EventMsg` variants needed for non-lossy app-server `ThreadItem` reconstruction. We now store the following ThreadItems that we didn't before: - web search - command execution - patch/file changes - MCP tool calls - image view calls - collab tool outcomes - context compaction - review mode enter/exit For **command executions** in particular, we truncate the output using the existing `truncate_text` from core to store an upper bound of 10,000 bytes, which is also the default value for truncating tool outputs shown to the model. This keeps the size of the rollout file and command execution items returned over the wire reasonable. And we also persist `EventMsg::Error` which we can now map back to the Turn's status and populates the Turn's error metadata. #### Updates to EventMsgs To truly make `thread/resume` non-lossy, we also needed to persist the `status` on `EventMsg::CommandExecutionEndEvent` and `EventMsg::PatchApplyEndEvent`. Previously it was not obvious whether a command failed or was declined (similar for apply_patch). These EventMsgs were never persisted before so I made it a required field.
2026-02-12 11:34:22 -08:00
"status",
"stderr",
"stdout",
"turn_id",
"type"
],
"title": "ExecCommandEndEventMsg",
"type": "object"
},
{
"description": "Notification that the agent attached a local image via the view_image tool.",
"properties": {
"call_id": {
"description": "Identifier for the originating tool call.",
"type": "string"
},
"path": {
"description": "Local filesystem path provided to the tool.",
"type": "string"
},
"type": {
"enum": [
"view_image_tool_call"
],
"title": "ViewImageToolCallEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"path",
"type"
],
"title": "ViewImageToolCallEventMsg",
"type": "object"
},
{
"properties": {
"additional_permissions": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfile"
},
{
"type": "null"
}
],
"description": "Optional additional filesystem permissions requested for this command."
},
"approval_id": {
"description": "Identifier for this specific approval callback.\n\nWhen absent, the approval is for the command item itself (`call_id`). This is present for subcommand approvals (via execve intercept).",
"type": [
"string",
"null"
]
},
feat: include available decisions in command approval requests (#12758) Command-approval clients currently infer which choices to show from side-channel fields like `networkApprovalContext`, `proposedExecpolicyAmendment`, and `additionalPermissions`. That makes the request shape harder to evolve, and it forces each client to replicate the server's heuristics instead of receiving the exact decision list for the prompt. This PR introduces a mapping between `CommandExecutionApprovalDecision` and `codex_protocol::protocol::ReviewDecision`: ```rust impl From<CoreReviewDecision> for CommandExecutionApprovalDecision { fn from(value: CoreReviewDecision) -> Self { match value { CoreReviewDecision::Approved => Self::Accept, CoreReviewDecision::ApprovedExecpolicyAmendment { proposed_execpolicy_amendment, } => Self::AcceptWithExecpolicyAmendment { execpolicy_amendment: proposed_execpolicy_amendment.into(), }, CoreReviewDecision::ApprovedForSession => Self::AcceptForSession, CoreReviewDecision::NetworkPolicyAmendment { network_policy_amendment, } => Self::ApplyNetworkPolicyAmendment { network_policy_amendment: network_policy_amendment.into(), }, CoreReviewDecision::Abort => Self::Cancel, CoreReviewDecision::Denied => Self::Decline, } } } ``` And updates `CommandExecutionRequestApprovalParams` to have a new field: ```rust available_decisions: Option<Vec<CommandExecutionApprovalDecision>> ``` when, if specified, should make it easier for clients to display an appropriate list of options in the UI. This makes it possible for `CoreShellActionProvider::prompt()` in `unix_escalation.rs` to specify the `Vec<ReviewDecision>` directly, adding support for `ApprovedForSession` when approving a skill script, which was previously missing in the TUI. Note this results in a significant change to `exec_options()` in `approval_overlay.rs`, as the displayed options are now derived from `available_decisions: &[ReviewDecision]`. ## What Changed - Add `available_decisions` to [`ExecApprovalRequestEvent`](https://github.com/openai/codex/blob/de00e932dd9801de0a4faac0519162099753f331/codex-rs/protocol/src/approvals.rs#L111-L175), including helpers to derive the legacy default choices when older senders omit the field. - Map `codex_protocol::protocol::ReviewDecision` to app-server `CommandExecutionApprovalDecision` and expose the ordered list as experimental `availableDecisions` in [`CommandExecutionRequestApprovalParams`](https://github.com/openai/codex/blob/de00e932dd9801de0a4faac0519162099753f331/codex-rs/app-server-protocol/src/protocol/v2.rs#L3798-L3807). - Thread optional `available_decisions` through the core approval path so Unix shell escalation can explicitly request `ApprovedForSession` for session-scoped approvals instead of relying on client heuristics. [`unix_escalation.rs`](https://github.com/openai/codex/blob/de00e932dd9801de0a4faac0519162099753f331/codex-rs/core/src/tools/runtimes/shell/unix_escalation.rs#L194-L214) - Update the TUI approval overlay to build its buttons from the ordered decision list, while preserving the legacy fallback when `available_decisions` is missing. - Update the app-server README, test client output, and generated schema artifacts to document and surface the new field. ## Testing - Add `approval_overlay.rs` coverage for explicit decision lists, including the generic `ApprovedForSession` path and network approval options. - Update `chatwidget/tests.rs` and app-server protocol tests to populate the new optional field and keep older event shapes working. ## Developers Docs - If we document `item/commandExecution/requestApproval` on [developers.openai.com/codex](https://developers.openai.com/codex), add experimental `availableDecisions` as the preferred source of approval choices and note that older servers may omit it.
2026-02-25 17:10:46 -08:00
"available_decisions": {
"description": "Ordered list of decisions the client may present for this prompt.\n\nWhen absent, clients should derive the legacy default set from the other fields on this request.",
"items": {
"$ref": "#/definitions/ReviewDecision"
},
"type": [
"array",
"null"
]
},
"call_id": {
"description": "Identifier for the associated command execution item.",
"type": "string"
},
"command": {
"description": "The command to be executed.",
"items": {
"type": "string"
},
"type": "array"
},
"cwd": {
"description": "The command's working directory.",
"type": "string"
},
"network_approval_context": {
"anyOf": [
{
"$ref": "#/definitions/NetworkApprovalContext"
},
{
"type": "null"
}
],
"description": "Optional network context for a blocked request that can be approved."
},
"parsed_cmd": {
"items": {
"$ref": "#/definitions/ParsedCommand"
},
"type": "array"
},
"proposed_execpolicy_amendment": {
"description": "Proposed execpolicy amendment that can be applied to allow future runs.",
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
},
"proposed_network_policy_amendments": {
"description": "Proposed network policy amendments (for example allow/deny this host in future).",
"items": {
"$ref": "#/definitions/NetworkPolicyAmendment"
},
"type": [
"array",
"null"
]
},
"reason": {
"description": "Optional human-readable reason for the approval (e.g. retry without sandbox).",
"type": [
"string",
"null"
]
},
app-server: include experimental skill metadata in exec approval requests (#13929) ## Summary This change surfaces skill metadata on command approval requests so app-server clients can tell when an approval came from a skill script and identify the originating `SKILL.md`. - add `skill_metadata` to exec approval events in the shared protocol - thread skill metadata through core shell escalation and delegated approval handling for skill-triggered approvals - expose the field in app-server v2 as experimental `skillMetadata` - regenerate the JSON/TypeScript schemas and cover the new field in protocol, transport, core, and TUI tests ## Why Skill-triggered approvals already carry skill context inside core, but app-server clients could not see which skill caused the prompt. Sending the skill metadata with the approval request makes it possible for clients to present better approval UX and connect the prompt back to the relevant skill definition. ## example event in app-server-v2 verified that we see this event when experimental api is on: ``` < { < "id": 11, < "method": "item/commandExecution/requestApproval", < "params": { < "additionalPermissions": { < "fileSystem": null, < "macos": { < "accessibility": false, < "automations": { < "bundle_ids": [ < "com.apple.Notes" < ] < }, < "calendar": false, < "preferences": "read_only" < }, < "network": null < }, < "approvalId": "25d600ee-5a3c-4746-8d17-e2e61fb4c563", < "availableDecisions": [ < "accept", < "acceptForSession", < "cancel" < ], < "command": "/Applications/ChatGPT.app/Contents/Resources/CodexAppServer_CodexAppServerBundledSkills.bundle/Contents/Resources/skills/apple-notes/scripts/notes_info", < "commandActions": [ < { < "command": "/Applications/ChatGPT.app/Contents/Resources/CodexAppServer_CodexAppServerBundledSkills.bundle/Contents/Resources/skills/apple-notes/scripts/notes_info", < "type": "unknown" < } < ], < "cwd": "/Applications/ChatGPT.app/Contents/Resources/CodexAppServer_CodexAppServerBundledSkills.bundle/Contents/Resources/skills/apple-notes", < "itemId": "call_jZp3xFpNg4D8iKAD49cvEvZy", < "skillMetadata": { < "pathToSkillsMd": "/Applications/ChatGPT.app/Contents/Resources/CodexAppServer_CodexAppServerBundledSkills.bundle/Contents/Resources/skills/apple-notes/SKILL.md" < }, < "threadId": "019ccc10-b7d3-7ff2-84fe-3a75e7681e69", < "turnId": "019ccc10-b848-76f1-81b3-4a1fa225493f" < } < }` ``` & verified that this is the event when experimental api is off: ``` < { < "id": 13, < "method": "item/commandExecution/requestApproval", < "params": { < "approvalId": "5fbbf776-261b-4cf8-899b-c125b547f2c0", < "availableDecisions": [ < "accept", < "acceptForSession", < "cancel" < ], < "command": "/Applications/ChatGPT.app/Contents/Resources/CodexAppServer_CodexAppServerBundledSkills.bundle/Contents/Resources/skills/apple-notes/scripts/notes_info", < "commandActions": [ < { < "command": "/Applications/ChatGPT.app/Contents/Resources/CodexAppServer_CodexAppServerBundledSkills.bundle/Contents/Resources/skills/apple-notes/scripts/notes_info", < "type": "unknown" < } < ], < "cwd": "/Users/celia/code/codex/codex-rs", < "itemId": "call_OV2DHzTgYcbYtWaTTBWlocOt", < "threadId": "019ccc16-2a2b-7be1-8500-e00d45b892d4", < "turnId": "019ccc16-2a8e-7961-98ec-649600e7d06a" < } < } ```
2026-03-08 18:07:46 -07:00
"skill_metadata": {
"anyOf": [
{
"$ref": "#/definitions/ExecApprovalRequestSkillMetadata"
},
{
"type": "null"
}
],
"description": "Optional skill metadata when the approval was triggered by a skill script."
},
"turn_id": {
"default": "",
"description": "Turn ID that this command belongs to. Uses `#[serde(default)]` for backwards compatibility.",
"type": "string"
},
"type": {
"enum": [
"exec_approval_request"
],
"title": "ExecApprovalRequestEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"command",
"cwd",
"parsed_cmd",
"type"
],
"title": "ExecApprovalRequestEventMsg",
"type": "object"
},
{
"properties": {
"call_id": {
"description": "Responses API call id for the associated tool call, if available.",
"type": "string"
},
"permissions": {
"$ref": "#/definitions/PermissionProfile"
},
"reason": {
"type": [
"string",
"null"
]
},
"turn_id": {
"default": "",
"description": "Turn ID that this request belongs to. Uses `#[serde(default)]` for backwards compatibility.",
"type": "string"
},
"type": {
"enum": [
"request_permissions"
],
"title": "RequestPermissionsEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"permissions",
"type"
],
"title": "RequestPermissionsEventMsg",
"type": "object"
},
{
"properties": {
"call_id": {
"description": "Responses API call id for the associated tool call, if available.",
"type": "string"
},
"questions": {
"items": {
"$ref": "#/definitions/RequestUserInputQuestion"
},
"type": "array"
},
"turn_id": {
"default": "",
"description": "Turn ID that this request belongs to. Uses `#[serde(default)]` for backwards compatibility.",
"type": "string"
},
"type": {
"enum": [
"request_user_input"
],
"title": "RequestUserInputEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"questions",
"type"
],
"title": "RequestUserInputEventMsg",
"type": "object"
},
{
"properties": {
"arguments": true,
"callId": {
"type": "string"
},
"tool": {
"type": "string"
},
"turnId": {
"type": "string"
},
"type": {
"enum": [
"dynamic_tool_call_request"
],
"title": "DynamicToolCallRequestEventMsgType",
"type": "string"
}
},
"required": [
"arguments",
"callId",
"tool",
"turnId",
"type"
],
"title": "DynamicToolCallRequestEventMsg",
"type": "object"
},
{
"properties": {
"arguments": {
"description": "Dynamic tool call arguments."
},
"call_id": {
"description": "Identifier for the corresponding DynamicToolCallRequest.",
"type": "string"
},
"content_items": {
"description": "Dynamic tool response content items.",
"items": {
"$ref": "#/definitions/DynamicToolCallOutputContentItem"
},
"type": "array"
},
"duration": {
"allOf": [
{
"$ref": "#/definitions/Duration"
}
],
"description": "The duration of the dynamic tool call."
},
"error": {
"description": "Optional error text when the tool call failed before producing a response.",
"type": [
"string",
"null"
]
},
"success": {
"description": "Whether the tool call succeeded.",
"type": "boolean"
},
"tool": {
"description": "Dynamic tool name.",
"type": "string"
},
"turn_id": {
"description": "Turn ID that this dynamic tool call belongs to.",
"type": "string"
},
"type": {
"enum": [
"dynamic_tool_call_response"
],
"title": "DynamicToolCallResponseEventMsgType",
"type": "string"
}
},
"required": [
"arguments",
"call_id",
"content_items",
"duration",
"success",
"tool",
"turn_id",
"type"
],
"title": "DynamicToolCallResponseEventMsg",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
feat(app-server): support mcp elicitations in v2 api (#13425) This adds a first-class server request for MCP server elicitations: `mcpServer/elicitation/request`. Until now, MCP elicitation requests only showed up as a raw `codex/event/elicitation_request` event from core. That made it hard for v2 clients to handle elicitations using the same request/response flow as other server-driven interactions (like shell and `apply_patch` tools). This also updates the underlying MCP elicitation request handling in core to pass through the full MCP request (including URL and form data) so we can expose it properly in app-server. ### Why not `item/mcpToolCall/elicitationRequest`? This is because MCP elicitations are related to MCP servers first, and only optionally to a specific MCP tool call. In the MCP protocol, elicitation is a server-to-client capability: the server sends `elicitation/create`, and the client replies with an elicitation result. RMCP models it that way as well. In practice an elicitation is often triggered by an MCP tool call, but not always. ### What changed - add `mcpServer/elicitation/request` to the v2 app-server API - translate core `codex/event/elicitation_request` events into the new v2 server request - map client responses back into `Op::ResolveElicitation` so the MCP server can continue - update app-server docs and generated protocol schema - add an end-to-end app-server test that covers the full round trip through a real RMCP elicitation flow - The new test exercises a realistic case where an MCP tool call triggers an elicitation, the app-server emits mcpServer/elicitation/request, the client accepts it, and the tool call resumes and completes successfully. ### app-server API flow - Client starts a thread with `thread/start`. - Client starts a turn with `turn/start`. - App-server sends `item/started` for the `mcpToolCall`. - While that tool call is in progress, app-server sends `mcpServer/elicitation/request`. - Client responds to that request with `{ action: "accept" | "decline" | "cancel" }`. - App-server sends `serverRequest/resolved`. - App-server sends `item/completed` for the mcpToolCall. - App-server sends `turn/completed`. - If the turn is interrupted while the elicitation is pending, app-server still sends `serverRequest/resolved` before the turn finishes.
2026-03-05 07:20:20 -08:00
"request": {
"$ref": "#/definitions/ElicitationRequest"
},
"server_name": {
"type": "string"
},
"turn_id": {
"description": "Turn ID that this elicitation belongs to, when known.",
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"elicitation_request"
],
"title": "ElicitationRequestEventMsgType",
"type": "string"
}
},
"required": [
"id",
feat(app-server): support mcp elicitations in v2 api (#13425) This adds a first-class server request for MCP server elicitations: `mcpServer/elicitation/request`. Until now, MCP elicitation requests only showed up as a raw `codex/event/elicitation_request` event from core. That made it hard for v2 clients to handle elicitations using the same request/response flow as other server-driven interactions (like shell and `apply_patch` tools). This also updates the underlying MCP elicitation request handling in core to pass through the full MCP request (including URL and form data) so we can expose it properly in app-server. ### Why not `item/mcpToolCall/elicitationRequest`? This is because MCP elicitations are related to MCP servers first, and only optionally to a specific MCP tool call. In the MCP protocol, elicitation is a server-to-client capability: the server sends `elicitation/create`, and the client replies with an elicitation result. RMCP models it that way as well. In practice an elicitation is often triggered by an MCP tool call, but not always. ### What changed - add `mcpServer/elicitation/request` to the v2 app-server API - translate core `codex/event/elicitation_request` events into the new v2 server request - map client responses back into `Op::ResolveElicitation` so the MCP server can continue - update app-server docs and generated protocol schema - add an end-to-end app-server test that covers the full round trip through a real RMCP elicitation flow - The new test exercises a realistic case where an MCP tool call triggers an elicitation, the app-server emits mcpServer/elicitation/request, the client accepts it, and the tool call resumes and completes successfully. ### app-server API flow - Client starts a thread with `thread/start`. - Client starts a turn with `turn/start`. - App-server sends `item/started` for the `mcpToolCall`. - While that tool call is in progress, app-server sends `mcpServer/elicitation/request`. - Client responds to that request with `{ action: "accept" | "decline" | "cancel" }`. - App-server sends `serverRequest/resolved`. - App-server sends `item/completed` for the mcpToolCall. - App-server sends `turn/completed`. - If the turn is interrupted while the elicitation is pending, app-server still sends `serverRequest/resolved` before the turn finishes.
2026-03-05 07:20:20 -08:00
"request",
"server_name",
"type"
],
"title": "ElicitationRequestEventMsg",
"type": "object"
},
{
"properties": {
"call_id": {
"description": "Responses API call id for the associated patch apply call, if available.",
"type": "string"
},
"changes": {
"additionalProperties": {
"$ref": "#/definitions/FileChange"
},
"type": "object"
},
"grant_root": {
"description": "When set, the agent is asking the user to allow writes under this root for the remainder of the session.",
"type": [
"string",
"null"
]
},
"reason": {
"description": "Optional explanatory reason (e.g. request for extra write access).",
"type": [
"string",
"null"
]
},
"turn_id": {
"default": "",
"description": "Turn ID that this patch belongs to. Uses `#[serde(default)]` for backwards compatibility with older senders.",
"type": "string"
},
"type": {
"enum": [
"apply_patch_approval_request"
],
"title": "ApplyPatchApprovalRequestEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"changes",
"type"
],
"title": "ApplyPatchApprovalRequestEventMsg",
"type": "object"
},
{
"description": "Notification advising the user that something they are using has been deprecated and should be phased out.",
"properties": {
"details": {
"description": "Optional extra guidance, such as migration steps or rationale.",
"type": [
"string",
"null"
]
},
"summary": {
"description": "Concise summary of what is deprecated.",
"type": "string"
},
"type": {
"enum": [
"deprecation_notice"
],
"title": "DeprecationNoticeEventMsgType",
"type": "string"
}
},
"required": [
"summary",
"type"
],
"title": "DeprecationNoticeEventMsg",
"type": "object"
},
{
"properties": {
"message": {
"type": "string"
},
"type": {
"enum": [
"background_event"
],
"title": "BackgroundEventEventMsgType",
"type": "string"
}
},
"required": [
"message",
"type"
],
"title": "BackgroundEventEventMsg",
"type": "object"
},
{
"properties": {
"message": {
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"undo_started"
],
"title": "UndoStartedEventMsgType",
"type": "string"
}
},
"required": [
"type"
],
"title": "UndoStartedEventMsg",
"type": "object"
},
{
"properties": {
"message": {
"type": [
"string",
"null"
]
},
"success": {
"type": "boolean"
},
"type": {
"enum": [
"undo_completed"
],
"title": "UndoCompletedEventMsgType",
"type": "string"
}
},
"required": [
"success",
"type"
],
"title": "UndoCompletedEventMsg",
"type": "object"
},
{
"description": "Notification that a model stream experienced an error or disconnect and the system is handling it (e.g., retrying with backoff).",
"properties": {
"additional_details": {
"default": null,
"description": "Optional details about the underlying stream failure (often the same human-readable message that is surfaced as the terminal error if retries are exhausted).",
"type": [
"string",
"null"
]
},
"codex_error_info": {
"anyOf": [
{
"$ref": "#/definitions/CodexErrorInfo"
},
{
"type": "null"
}
],
"default": null
},
"message": {
"type": "string"
},
"type": {
"enum": [
"stream_error"
],
"title": "StreamErrorEventMsgType",
"type": "string"
}
},
"required": [
"message",
"type"
],
"title": "StreamErrorEventMsg",
"type": "object"
},
{
"description": "Notification that the agent is about to apply a code patch. Mirrors `ExecCommandBegin` so frontends can show progress indicators.",
"properties": {
"auto_approved": {
"description": "If true, there was no ApplyPatchApprovalRequest for this patch.",
"type": "boolean"
},
"call_id": {
"description": "Identifier so this can be paired with the PatchApplyEnd event.",
"type": "string"
},
"changes": {
"additionalProperties": {
"$ref": "#/definitions/FileChange"
},
"description": "The changes to be applied.",
"type": "object"
},
"turn_id": {
"default": "",
"description": "Turn ID that this patch belongs to. Uses `#[serde(default)]` for backwards compatibility.",
"type": "string"
},
"type": {
"enum": [
"patch_apply_begin"
],
"title": "PatchApplyBeginEventMsgType",
"type": "string"
}
},
"required": [
"auto_approved",
"call_id",
"changes",
"type"
],
"title": "PatchApplyBeginEventMsg",
"type": "object"
},
{
"description": "Notification that a patch application has finished.",
"properties": {
"call_id": {
"description": "Identifier for the PatchApplyBegin that finished.",
"type": "string"
},
"changes": {
"additionalProperties": {
"$ref": "#/definitions/FileChange"
},
"default": {},
"description": "The changes that were applied (mirrors PatchApplyBeginEvent::changes).",
"type": "object"
},
feat(app-server): experimental flag to persist extended history (#11227) This PR adds an experimental `persist_extended_history` bool flag to app-server thread APIs so rollout logs can retain a richer set of EventMsgs for non-lossy Thread > Turn > ThreadItems reconstruction (i.e. on `thread/resume`). ### Motivation Today, our rollout recorder only persists a small subset (e.g. user message, reasoning, assistant message) of `EventMsg` types, dropping a good number (like command exec, file change, etc.) that are important for reconstructing full item history for `thread/resume`, `thread/read`, and `thread/fork`. Some clients want to be able to resume a thread without lossiness. This lossiness is primarily a UI thing, since what the model sees are `ResponseItem` and not `EventMsg`. ### Approach This change introduces an opt-in `persist_full_history` flag to preserve those events when you start/resume/fork a thread (defaults to `false`). This is done by adding an `EventPersistenceMode` to the rollout recorder: - `Limited` (existing behavior, default) - `Extended` (new opt-in behavior) In `Extended` mode, persist additional `EventMsg` variants needed for non-lossy app-server `ThreadItem` reconstruction. We now store the following ThreadItems that we didn't before: - web search - command execution - patch/file changes - MCP tool calls - image view calls - collab tool outcomes - context compaction - review mode enter/exit For **command executions** in particular, we truncate the output using the existing `truncate_text` from core to store an upper bound of 10,000 bytes, which is also the default value for truncating tool outputs shown to the model. This keeps the size of the rollout file and command execution items returned over the wire reasonable. And we also persist `EventMsg::Error` which we can now map back to the Turn's status and populates the Turn's error metadata. #### Updates to EventMsgs To truly make `thread/resume` non-lossy, we also needed to persist the `status` on `EventMsg::CommandExecutionEndEvent` and `EventMsg::PatchApplyEndEvent`. Previously it was not obvious whether a command failed or was declined (similar for apply_patch). These EventMsgs were never persisted before so I made it a required field.
2026-02-12 11:34:22 -08:00
"status": {
"allOf": [
{
"$ref": "#/definitions/PatchApplyStatus"
}
],
"description": "Completion status for this patch application."
},
"stderr": {
"description": "Captured stderr (parser errors, IO failures, etc.).",
"type": "string"
},
"stdout": {
"description": "Captured stdout (summary printed by apply_patch).",
"type": "string"
},
"success": {
"description": "Whether the patch was applied successfully.",
"type": "boolean"
},
"turn_id": {
"default": "",
"description": "Turn ID that this patch belongs to. Uses `#[serde(default)]` for backwards compatibility.",
"type": "string"
},
"type": {
"enum": [
"patch_apply_end"
],
"title": "PatchApplyEndEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
feat(app-server): experimental flag to persist extended history (#11227) This PR adds an experimental `persist_extended_history` bool flag to app-server thread APIs so rollout logs can retain a richer set of EventMsgs for non-lossy Thread > Turn > ThreadItems reconstruction (i.e. on `thread/resume`). ### Motivation Today, our rollout recorder only persists a small subset (e.g. user message, reasoning, assistant message) of `EventMsg` types, dropping a good number (like command exec, file change, etc.) that are important for reconstructing full item history for `thread/resume`, `thread/read`, and `thread/fork`. Some clients want to be able to resume a thread without lossiness. This lossiness is primarily a UI thing, since what the model sees are `ResponseItem` and not `EventMsg`. ### Approach This change introduces an opt-in `persist_full_history` flag to preserve those events when you start/resume/fork a thread (defaults to `false`). This is done by adding an `EventPersistenceMode` to the rollout recorder: - `Limited` (existing behavior, default) - `Extended` (new opt-in behavior) In `Extended` mode, persist additional `EventMsg` variants needed for non-lossy app-server `ThreadItem` reconstruction. We now store the following ThreadItems that we didn't before: - web search - command execution - patch/file changes - MCP tool calls - image view calls - collab tool outcomes - context compaction - review mode enter/exit For **command executions** in particular, we truncate the output using the existing `truncate_text` from core to store an upper bound of 10,000 bytes, which is also the default value for truncating tool outputs shown to the model. This keeps the size of the rollout file and command execution items returned over the wire reasonable. And we also persist `EventMsg::Error` which we can now map back to the Turn's status and populates the Turn's error metadata. #### Updates to EventMsgs To truly make `thread/resume` non-lossy, we also needed to persist the `status` on `EventMsg::CommandExecutionEndEvent` and `EventMsg::PatchApplyEndEvent`. Previously it was not obvious whether a command failed or was declined (similar for apply_patch). These EventMsgs were never persisted before so I made it a required field.
2026-02-12 11:34:22 -08:00
"status",
"stderr",
"stdout",
"success",
"type"
],
"title": "PatchApplyEndEventMsg",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"turn_diff"
],
"title": "TurnDiffEventMsgType",
"type": "string"
},
"unified_diff": {
"type": "string"
}
},
"required": [
"type",
"unified_diff"
],
"title": "TurnDiffEventMsg",
"type": "object"
},
{
"description": "Response to GetHistoryEntryRequest.",
"properties": {
"entry": {
"anyOf": [
{
"$ref": "#/definitions/HistoryEntry"
},
{
"type": "null"
}
],
"description": "The entry at the requested offset, if available and parseable."
},
"log_id": {
"format": "uint64",
"minimum": 0.0,
"type": "integer"
},
"offset": {
"format": "uint",
"minimum": 0.0,
"type": "integer"
},
"type": {
"enum": [
"get_history_entry_response"
],
"title": "GetHistoryEntryResponseEventMsgType",
"type": "string"
}
},
"required": [
"log_id",
"offset",
"type"
],
"title": "GetHistoryEntryResponseEventMsg",
"type": "object"
},
{
"description": "List of MCP tools available to the agent.",
"properties": {
"auth_statuses": {
"additionalProperties": {
"$ref": "#/definitions/McpAuthStatus"
},
"description": "Authentication status for each configured MCP server.",
"type": "object"
},
"resource_templates": {
"additionalProperties": {
"items": {
"$ref": "#/definitions/ResourceTemplate"
},
"type": "array"
},
"description": "Known resource templates grouped by server name.",
"type": "object"
},
"resources": {
"additionalProperties": {
"items": {
"$ref": "#/definitions/Resource"
},
"type": "array"
},
"description": "Known resources grouped by server name.",
"type": "object"
},
"tools": {
"additionalProperties": {
"$ref": "#/definitions/Tool"
},
"description": "Fully qualified tool name -> tool definition.",
"type": "object"
},
"type": {
"enum": [
"mcp_list_tools_response"
],
"title": "McpListToolsResponseEventMsgType",
"type": "string"
}
},
"required": [
"auth_statuses",
"resource_templates",
"resources",
"tools",
"type"
],
"title": "McpListToolsResponseEventMsg",
"type": "object"
},
{
"description": "List of custom prompts available to the agent.",
"properties": {
"custom_prompts": {
"items": {
"$ref": "#/definitions/CustomPrompt"
},
"type": "array"
},
"type": {
"enum": [
"list_custom_prompts_response"
],
"title": "ListCustomPromptsResponseEventMsgType",
"type": "string"
}
},
"required": [
"custom_prompts",
"type"
],
"title": "ListCustomPromptsResponseEventMsg",
"type": "object"
},
{
"description": "List of skills available to the agent.",
"properties": {
"skills": {
"items": {
"$ref": "#/definitions/SkillsListEntry"
},
"type": "array"
},
"type": {
"enum": [
"list_skills_response"
],
"title": "ListSkillsResponseEventMsgType",
"type": "string"
}
},
"required": [
"skills",
"type"
],
"title": "ListSkillsResponseEventMsg",
"type": "object"
},
{
"description": "List of remote skills available to the agent.",
"properties": {
"skills": {
"items": {
"$ref": "#/definitions/RemoteSkillSummary"
},
"type": "array"
},
"type": {
"enum": [
"list_remote_skills_response"
],
"title": "ListRemoteSkillsResponseEventMsgType",
"type": "string"
}
},
"required": [
"skills",
"type"
],
"title": "ListRemoteSkillsResponseEventMsg",
"type": "object"
},
{
"description": "Remote skill downloaded to local cache.",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"path": {
"type": "string"
},
"type": {
"enum": [
"remote_skill_downloaded"
],
"title": "RemoteSkillDownloadedEventMsgType",
"type": "string"
}
},
"required": [
"id",
"name",
"path",
"type"
],
"title": "RemoteSkillDownloadedEventMsg",
"type": "object"
},
{
"description": "Notification that skill data may have been updated and clients may want to reload.",
"properties": {
"type": {
"enum": [
"skills_update_available"
],
"title": "SkillsUpdateAvailableEventMsgType",
"type": "string"
}
},
"required": [
"type"
],
"title": "SkillsUpdateAvailableEventMsg",
"type": "object"
},
{
"properties": {
"explanation": {
"default": null,
"description": "Arguments for the `update_plan` todo/checklist tool (not plan mode).",
"type": [
"string",
"null"
]
},
"plan": {
"items": {
"$ref": "#/definitions/PlanItemArg"
},
"type": "array"
},
"type": {
"enum": [
"plan_update"
],
"title": "PlanUpdateEventMsgType",
"type": "string"
}
},
"required": [
"plan",
"type"
],
"title": "PlanUpdateEventMsg",
"type": "object"
},
{
"properties": {
"reason": {
"$ref": "#/definitions/TurnAbortReason"
},
chore: persist turn_id in rollout session and make turn_id uuid based (#11246) Problem: 1. turn id is constructed in-memory; 2. on resuming threads, turn_id might not be unique; 3. client cannot no the boundary of a turn from rollout files easily. This PR does three things: 1. persist `task_started` and `task_complete` events; 1. persist `turn_id` in rollout turn events; 5. generate turn_id as unique uuids instead of incrementing it in memory. This helps us resolve the issue of clients wanting to have unique turn ids for resuming a thread, and knowing the boundry of each turn in rollout files. example debug logs ``` 2026-02-11T00:32:10.746876Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=8 turn=Turn { id: "019c4a07-d809-74c3-bc4b-fd9618487b4b", items: [UserMessage { id: "item-24", content: [Text { text: "hi", text_elements: [] }] }, AgentMessage { id: "item-25", text: "Hi. I’m in the workspace with your current changes loaded and ready. Send the next task and I’ll execute it end-to-end." }], status: Completed, error: None } 2026-02-11T00:32:10.746888Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=9 turn=Turn { id: "019c4a18-1004-76c0-a0fb-a77610f6a9b8", items: [UserMessage { id: "item-26", content: [Text { text: "hello", text_elements: [] }] }, AgentMessage { id: "item-27", text: "Hello. Ready for the next change in `codex-rs`; I can continue from the current in-progress diff or start a new task." }], status: Completed, error: None } 2026-02-11T00:32:10.746899Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=10 turn=Turn { id: "019c4a19-41f0-7db0-ad78-74f1503baeb8", items: [UserMessage { id: "item-28", content: [Text { text: "hello", text_elements: [] }] }, AgentMessage { id: "item-29", text: "Hello. Send the specific change you want in `codex-rs`, and I’ll implement it and run the required checks." }], status: Completed, error: None } ``` backward compatibility: if you try to resume an old session without task_started and task_complete event populated, the following happens: - If you resume and do nothing: those reconstructed historical IDs can differ next time you resume. - If you resume and send a new turn: the new turn gets a fresh UUID from live submission flow and is persisted, so that new turn’s ID is stable on later resumes. I think this behavior is fine, because we only care about deterministic turn id once a turn is triggered.
2026-02-10 19:56:01 -08:00
"turn_id": {
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"turn_aborted"
],
"title": "TurnAbortedEventMsgType",
"type": "string"
}
},
"required": [
"reason",
"type"
],
"title": "TurnAbortedEventMsg",
"type": "object"
},
{
"description": "Notification that the agent is shutting down.",
"properties": {
"type": {
"enum": [
"shutdown_complete"
],
"title": "ShutdownCompleteEventMsgType",
"type": "string"
}
},
"required": [
"type"
],
"title": "ShutdownCompleteEventMsg",
"type": "object"
},
{
"description": "Entered review mode.",
"properties": {
"target": {
"$ref": "#/definitions/ReviewTarget"
},
"type": {
"enum": [
"entered_review_mode"
],
"title": "EnteredReviewModeEventMsgType",
"type": "string"
},
"user_facing_hint": {
"type": [
"string",
"null"
]
}
},
"required": [
"target",
"type"
],
"title": "EnteredReviewModeEventMsg",
"type": "object"
},
{
"description": "Exited review mode with an optional final result to apply.",
"properties": {
"review_output": {
"anyOf": [
{
"$ref": "#/definitions/ReviewOutputEvent"
},
{
"type": "null"
}
]
},
"type": {
"enum": [
"exited_review_mode"
],
"title": "ExitedReviewModeEventMsgType",
"type": "string"
}
},
"required": [
"type"
],
"title": "ExitedReviewModeEventMsg",
"type": "object"
},
{
"properties": {
"item": {
"$ref": "#/definitions/ResponseItem"
},
"type": {
"enum": [
"raw_response_item"
],
"title": "RawResponseItemEventMsgType",
"type": "string"
}
},
"required": [
"item",
"type"
],
"title": "RawResponseItemEventMsg",
"type": "object"
},
{
"properties": {
"item": {
"$ref": "#/definitions/TurnItem"
},
"thread_id": {
"$ref": "#/definitions/ThreadId"
},
"turn_id": {
"type": "string"
},
"type": {
"enum": [
"item_started"
],
"title": "ItemStartedEventMsgType",
"type": "string"
}
},
"required": [
"item",
"thread_id",
"turn_id",
"type"
],
"title": "ItemStartedEventMsg",
"type": "object"
},
{
"properties": {
"item": {
"$ref": "#/definitions/TurnItem"
},
"thread_id": {
"$ref": "#/definitions/ThreadId"
},
"turn_id": {
"type": "string"
},
"type": {
"enum": [
"item_completed"
],
"title": "ItemCompletedEventMsgType",
"type": "string"
}
},
"required": [
"item",
"thread_id",
"turn_id",
"type"
],
"title": "ItemCompletedEventMsg",
"type": "object"
},
{
"properties": {
"run": {
"$ref": "#/definitions/HookRunSummary"
},
"turn_id": {
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"hook_started"
],
"title": "HookStartedEventMsgType",
"type": "string"
}
},
"required": [
"run",
"type"
],
"title": "HookStartedEventMsg",
"type": "object"
},
{
"properties": {
"run": {
"$ref": "#/definitions/HookRunSummary"
},
"turn_id": {
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"hook_completed"
],
"title": "HookCompletedEventMsgType",
"type": "string"
}
},
"required": [
"run",
"type"
],
"title": "HookCompletedEventMsg",
"type": "object"
},
{
"properties": {
"delta": {
"type": "string"
},
"item_id": {
"type": "string"
},
"thread_id": {
"type": "string"
},
"turn_id": {
"type": "string"
},
"type": {
"enum": [
"agent_message_content_delta"
],
"title": "AgentMessageContentDeltaEventMsgType",
"type": "string"
}
},
"required": [
"delta",
"item_id",
"thread_id",
"turn_id",
"type"
],
"title": "AgentMessageContentDeltaEventMsg",
"type": "object"
},
{
"properties": {
"delta": {
"type": "string"
},
"item_id": {
"type": "string"
},
"thread_id": {
"type": "string"
},
"turn_id": {
"type": "string"
},
"type": {
"enum": [
"plan_delta"
],
"title": "PlanDeltaEventMsgType",
"type": "string"
}
},
"required": [
"delta",
"item_id",
"thread_id",
"turn_id",
"type"
],
"title": "PlanDeltaEventMsg",
"type": "object"
},
{
"properties": {
"delta": {
"type": "string"
},
"item_id": {
"type": "string"
},
"summary_index": {
"default": 0,
"format": "int64",
"type": "integer"
},
"thread_id": {
"type": "string"
},
"turn_id": {
"type": "string"
},
"type": {
"enum": [
"reasoning_content_delta"
],
"title": "ReasoningContentDeltaEventMsgType",
"type": "string"
}
},
"required": [
"delta",
"item_id",
"thread_id",
"turn_id",
"type"
],
"title": "ReasoningContentDeltaEventMsg",
"type": "object"
},
{
"properties": {
"content_index": {
"default": 0,
"format": "int64",
"type": "integer"
},
"delta": {
"type": "string"
},
"item_id": {
"type": "string"
},
"thread_id": {
"type": "string"
},
"turn_id": {
"type": "string"
},
"type": {
"enum": [
"reasoning_raw_content_delta"
],
"title": "ReasoningRawContentDeltaEventMsgType",
"type": "string"
}
},
"required": [
"delta",
"item_id",
"thread_id",
"turn_id",
"type"
],
"title": "ReasoningRawContentDeltaEventMsg",
"type": "object"
},
{
"description": "Collab interaction: agent spawn begin.",
"properties": {
"call_id": {
"description": "Identifier for the collab tool call.",
"type": "string"
},
"model": {
"type": "string"
},
"prompt": {
"description": "Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the beginning.",
"type": "string"
},
"reasoning_effort": {
"$ref": "#/definitions/ReasoningEffort"
},
"sender_thread_id": {
"allOf": [
{
"$ref": "#/definitions/ThreadId"
}
],
"description": "Thread ID of the sender."
},
"type": {
"enum": [
"collab_agent_spawn_begin"
],
"title": "CollabAgentSpawnBeginEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"model",
"prompt",
"reasoning_effort",
"sender_thread_id",
"type"
],
"title": "CollabAgentSpawnBeginEventMsg",
"type": "object"
},
{
"description": "Collab interaction: agent spawn end.",
"properties": {
"call_id": {
"description": "Identifier for the collab tool call.",
"type": "string"
},
"new_agent_nickname": {
"description": "Optional nickname assigned to the new agent.",
"type": [
"string",
"null"
]
},
"new_agent_role": {
"description": "Optional role assigned to the new agent.",
"type": [
"string",
"null"
]
},
"new_thread_id": {
"anyOf": [
{
"$ref": "#/definitions/ThreadId"
},
{
"type": "null"
}
],
"description": "Thread ID of the newly spawned agent, if it was created."
},
"prompt": {
"description": "Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the beginning.",
"type": "string"
},
"sender_thread_id": {
"allOf": [
{
"$ref": "#/definitions/ThreadId"
}
],
"description": "Thread ID of the sender."
},
"status": {
"allOf": [
{
"$ref": "#/definitions/AgentStatus"
}
],
"description": "Last known status of the new agent reported to the sender agent."
},
"type": {
"enum": [
"collab_agent_spawn_end"
],
"title": "CollabAgentSpawnEndEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"prompt",
"sender_thread_id",
"status",
"type"
],
"title": "CollabAgentSpawnEndEventMsg",
"type": "object"
},
{
"description": "Collab interaction: agent interaction begin.",
"properties": {
"call_id": {
"description": "Identifier for the collab tool call.",
"type": "string"
},
"prompt": {
"description": "Prompt sent from the sender to the receiver. Can be empty to prevent CoT leaking at the beginning.",
"type": "string"
},
"receiver_thread_id": {
"allOf": [
{
"$ref": "#/definitions/ThreadId"
}
],
"description": "Thread ID of the receiver."
},
"sender_thread_id": {
"allOf": [
{
"$ref": "#/definitions/ThreadId"
}
],
"description": "Thread ID of the sender."
},
"type": {
"enum": [
"collab_agent_interaction_begin"
],
"title": "CollabAgentInteractionBeginEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"prompt",
"receiver_thread_id",
"sender_thread_id",
"type"
],
"title": "CollabAgentInteractionBeginEventMsg",
"type": "object"
},
{
"description": "Collab interaction: agent interaction end.",
"properties": {
"call_id": {
"description": "Identifier for the collab tool call.",
"type": "string"
},
"prompt": {
"description": "Prompt sent from the sender to the receiver. Can be empty to prevent CoT leaking at the beginning.",
"type": "string"
},
"receiver_agent_nickname": {
"description": "Optional nickname assigned to the receiver agent.",
"type": [
"string",
"null"
]
},
"receiver_agent_role": {
"description": "Optional role assigned to the receiver agent.",
"type": [
"string",
"null"
]
},
"receiver_thread_id": {
"allOf": [
{
"$ref": "#/definitions/ThreadId"
}
],
"description": "Thread ID of the receiver."
},
"sender_thread_id": {
"allOf": [
{
"$ref": "#/definitions/ThreadId"
}
],
"description": "Thread ID of the sender."
},
"status": {
"allOf": [
{
"$ref": "#/definitions/AgentStatus"
}
],
"description": "Last known status of the receiver agent reported to the sender agent."
},
"type": {
"enum": [
"collab_agent_interaction_end"
],
"title": "CollabAgentInteractionEndEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"prompt",
"receiver_thread_id",
"sender_thread_id",
"status",
"type"
],
"title": "CollabAgentInteractionEndEventMsg",
"type": "object"
},
{
"description": "Collab interaction: waiting begin.",
"properties": {
"call_id": {
"description": "ID of the waiting call.",
"type": "string"
},
"receiver_agents": {
"description": "Optional nicknames/roles for receivers.",
"items": {
"$ref": "#/definitions/CollabAgentRef"
},
"type": "array"
},
"receiver_thread_ids": {
"description": "Thread ID of the receivers.",
"items": {
"$ref": "#/definitions/ThreadId"
},
"type": "array"
},
"sender_thread_id": {
"allOf": [
{
"$ref": "#/definitions/ThreadId"
}
],
"description": "Thread ID of the sender."
},
"type": {
"enum": [
"collab_waiting_begin"
],
"title": "CollabWaitingBeginEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"receiver_thread_ids",
"sender_thread_id",
"type"
],
"title": "CollabWaitingBeginEventMsg",
"type": "object"
},
{
"description": "Collab interaction: waiting end.",
"properties": {
"agent_statuses": {
"description": "Optional receiver metadata paired with final statuses.",
"items": {
"$ref": "#/definitions/CollabAgentStatusEntry"
},
"type": "array"
},
"call_id": {
"description": "ID of the waiting call.",
"type": "string"
},
"sender_thread_id": {
"allOf": [
{
"$ref": "#/definitions/ThreadId"
}
],
"description": "Thread ID of the sender."
},
"statuses": {
"additionalProperties": {
"$ref": "#/definitions/AgentStatus"
},
"description": "Last known status of the receiver agents reported to the sender agent.",
"type": "object"
},
"type": {
"enum": [
"collab_waiting_end"
],
"title": "CollabWaitingEndEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"sender_thread_id",
"statuses",
"type"
],
"title": "CollabWaitingEndEventMsg",
"type": "object"
},
{
"description": "Collab interaction: close begin.",
"properties": {
"call_id": {
"description": "Identifier for the collab tool call.",
"type": "string"
},
"receiver_thread_id": {
"allOf": [
{
"$ref": "#/definitions/ThreadId"
}
],
"description": "Thread ID of the receiver."
},
"sender_thread_id": {
"allOf": [
{
"$ref": "#/definitions/ThreadId"
}
],
"description": "Thread ID of the sender."
},
"type": {
"enum": [
"collab_close_begin"
],
"title": "CollabCloseBeginEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"receiver_thread_id",
"sender_thread_id",
"type"
],
"title": "CollabCloseBeginEventMsg",
"type": "object"
},
{
"description": "Collab interaction: close end.",
"properties": {
"call_id": {
"description": "Identifier for the collab tool call.",
"type": "string"
},
"receiver_agent_nickname": {
"description": "Optional nickname assigned to the receiver agent.",
"type": [
"string",
"null"
]
},
"receiver_agent_role": {
"description": "Optional role assigned to the receiver agent.",
"type": [
"string",
"null"
]
},
"receiver_thread_id": {
"allOf": [
{
"$ref": "#/definitions/ThreadId"
}
],
"description": "Thread ID of the receiver."
},
"sender_thread_id": {
"allOf": [
{
"$ref": "#/definitions/ThreadId"
}
],
"description": "Thread ID of the sender."
},
"status": {
"allOf": [
{
"$ref": "#/definitions/AgentStatus"
}
],
"description": "Last known status of the receiver agent reported to the sender agent before the close."
},
"type": {
"enum": [
"collab_close_end"
],
"title": "CollabCloseEndEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"receiver_thread_id",
"sender_thread_id",
"status",
"type"
],
"title": "CollabCloseEndEventMsg",
"type": "object"
},
{
"description": "Collab interaction: resume begin.",
"properties": {
"call_id": {
"description": "Identifier for the collab tool call.",
"type": "string"
},
"receiver_agent_nickname": {
"description": "Optional nickname assigned to the receiver agent.",
"type": [
"string",
"null"
]
},
"receiver_agent_role": {
"description": "Optional role assigned to the receiver agent.",
"type": [
"string",
"null"
]
},
"receiver_thread_id": {
"allOf": [
{
"$ref": "#/definitions/ThreadId"
}
],
"description": "Thread ID of the receiver."
},
"sender_thread_id": {
"allOf": [
{
"$ref": "#/definitions/ThreadId"
}
],
"description": "Thread ID of the sender."
},
"type": {
"enum": [
"collab_resume_begin"
],
"title": "CollabResumeBeginEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"receiver_thread_id",
"sender_thread_id",
"type"
],
"title": "CollabResumeBeginEventMsg",
"type": "object"
},
{
"description": "Collab interaction: resume end.",
"properties": {
"call_id": {
"description": "Identifier for the collab tool call.",
"type": "string"
},
"receiver_agent_nickname": {
"description": "Optional nickname assigned to the receiver agent.",
"type": [
"string",
"null"
]
},
"receiver_agent_role": {
"description": "Optional role assigned to the receiver agent.",
"type": [
"string",
"null"
]
},
"receiver_thread_id": {
"allOf": [
{
"$ref": "#/definitions/ThreadId"
}
],
"description": "Thread ID of the receiver."
},
"sender_thread_id": {
"allOf": [
{
"$ref": "#/definitions/ThreadId"
}
],
"description": "Thread ID of the sender."
},
"status": {
"allOf": [
{
"$ref": "#/definitions/AgentStatus"
}
],
"description": "Last known status of the receiver agent reported to the sender agent after resume."
},
"type": {
"enum": [
"collab_resume_end"
],
"title": "CollabResumeEndEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"receiver_thread_id",
"sender_thread_id",
"status",
"type"
],
"title": "CollabResumeEndEventMsg",
"type": "object"
}
]
},
app-server: include experimental skill metadata in exec approval requests (#13929) ## Summary This change surfaces skill metadata on command approval requests so app-server clients can tell when an approval came from a skill script and identify the originating `SKILL.md`. - add `skill_metadata` to exec approval events in the shared protocol - thread skill metadata through core shell escalation and delegated approval handling for skill-triggered approvals - expose the field in app-server v2 as experimental `skillMetadata` - regenerate the JSON/TypeScript schemas and cover the new field in protocol, transport, core, and TUI tests ## Why Skill-triggered approvals already carry skill context inside core, but app-server clients could not see which skill caused the prompt. Sending the skill metadata with the approval request makes it possible for clients to present better approval UX and connect the prompt back to the relevant skill definition. ## example event in app-server-v2 verified that we see this event when experimental api is on: ``` < { < "id": 11, < "method": "item/commandExecution/requestApproval", < "params": { < "additionalPermissions": { < "fileSystem": null, < "macos": { < "accessibility": false, < "automations": { < "bundle_ids": [ < "com.apple.Notes" < ] < }, < "calendar": false, < "preferences": "read_only" < }, < "network": null < }, < "approvalId": "25d600ee-5a3c-4746-8d17-e2e61fb4c563", < "availableDecisions": [ < "accept", < "acceptForSession", < "cancel" < ], < "command": "/Applications/ChatGPT.app/Contents/Resources/CodexAppServer_CodexAppServerBundledSkills.bundle/Contents/Resources/skills/apple-notes/scripts/notes_info", < "commandActions": [ < { < "command": "/Applications/ChatGPT.app/Contents/Resources/CodexAppServer_CodexAppServerBundledSkills.bundle/Contents/Resources/skills/apple-notes/scripts/notes_info", < "type": "unknown" < } < ], < "cwd": "/Applications/ChatGPT.app/Contents/Resources/CodexAppServer_CodexAppServerBundledSkills.bundle/Contents/Resources/skills/apple-notes", < "itemId": "call_jZp3xFpNg4D8iKAD49cvEvZy", < "skillMetadata": { < "pathToSkillsMd": "/Applications/ChatGPT.app/Contents/Resources/CodexAppServer_CodexAppServerBundledSkills.bundle/Contents/Resources/skills/apple-notes/SKILL.md" < }, < "threadId": "019ccc10-b7d3-7ff2-84fe-3a75e7681e69", < "turnId": "019ccc10-b848-76f1-81b3-4a1fa225493f" < } < }` ``` & verified that this is the event when experimental api is off: ``` < { < "id": 13, < "method": "item/commandExecution/requestApproval", < "params": { < "approvalId": "5fbbf776-261b-4cf8-899b-c125b547f2c0", < "availableDecisions": [ < "accept", < "acceptForSession", < "cancel" < ], < "command": "/Applications/ChatGPT.app/Contents/Resources/CodexAppServer_CodexAppServerBundledSkills.bundle/Contents/Resources/skills/apple-notes/scripts/notes_info", < "commandActions": [ < { < "command": "/Applications/ChatGPT.app/Contents/Resources/CodexAppServer_CodexAppServerBundledSkills.bundle/Contents/Resources/skills/apple-notes/scripts/notes_info", < "type": "unknown" < } < ], < "cwd": "/Users/celia/code/codex/codex-rs", < "itemId": "call_OV2DHzTgYcbYtWaTTBWlocOt", < "threadId": "019ccc16-2a2b-7be1-8500-e00d45b892d4", < "turnId": "019ccc16-2a8e-7961-98ec-649600e7d06a" < } < } ```
2026-03-08 18:07:46 -07:00
"ExecApprovalRequestSkillMetadata": {
"properties": {
"path_to_skills_md": {
"type": "string"
}
},
"required": [
"path_to_skills_md"
],
"type": "object"
},
"ExecCommandSource": {
"enum": [
"agent",
"user_shell",
"unified_exec_startup",
"unified_exec_interaction"
],
"type": "string"
},
feat(app-server): experimental flag to persist extended history (#11227) This PR adds an experimental `persist_extended_history` bool flag to app-server thread APIs so rollout logs can retain a richer set of EventMsgs for non-lossy Thread > Turn > ThreadItems reconstruction (i.e. on `thread/resume`). ### Motivation Today, our rollout recorder only persists a small subset (e.g. user message, reasoning, assistant message) of `EventMsg` types, dropping a good number (like command exec, file change, etc.) that are important for reconstructing full item history for `thread/resume`, `thread/read`, and `thread/fork`. Some clients want to be able to resume a thread without lossiness. This lossiness is primarily a UI thing, since what the model sees are `ResponseItem` and not `EventMsg`. ### Approach This change introduces an opt-in `persist_full_history` flag to preserve those events when you start/resume/fork a thread (defaults to `false`). This is done by adding an `EventPersistenceMode` to the rollout recorder: - `Limited` (existing behavior, default) - `Extended` (new opt-in behavior) In `Extended` mode, persist additional `EventMsg` variants needed for non-lossy app-server `ThreadItem` reconstruction. We now store the following ThreadItems that we didn't before: - web search - command execution - patch/file changes - MCP tool calls - image view calls - collab tool outcomes - context compaction - review mode enter/exit For **command executions** in particular, we truncate the output using the existing `truncate_text` from core to store an upper bound of 10,000 bytes, which is also the default value for truncating tool outputs shown to the model. This keeps the size of the rollout file and command execution items returned over the wire reasonable. And we also persist `EventMsg::Error` which we can now map back to the Turn's status and populates the Turn's error metadata. #### Updates to EventMsgs To truly make `thread/resume` non-lossy, we also needed to persist the `status` on `EventMsg::CommandExecutionEndEvent` and `EventMsg::PatchApplyEndEvent`. Previously it was not obvious whether a command failed or was declined (similar for apply_patch). These EventMsgs were never persisted before so I made it a required field.
2026-02-12 11:34:22 -08:00
"ExecCommandStatus": {
"enum": [
"completed",
"failed",
"declined"
],
"type": "string"
},
"ExecOutputStream": {
"enum": [
"stdout",
"stderr"
],
"type": "string"
},
"FileChange": {
"oneOf": [
{
"properties": {
"content": {
"type": "string"
},
"type": {
"enum": [
"add"
],
"title": "AddFileChangeType",
"type": "string"
}
},
"required": [
"content",
"type"
],
"title": "AddFileChange",
"type": "object"
},
{
"properties": {
"content": {
"type": "string"
},
"type": {
"enum": [
"delete"
],
"title": "DeleteFileChangeType",
"type": "string"
}
},
"required": [
"content",
"type"
],
"title": "DeleteFileChange",
"type": "object"
},
{
"properties": {
"move_path": {
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"update"
],
"title": "UpdateFileChangeType",
"type": "string"
},
"unified_diff": {
"type": "string"
}
},
"required": [
"type",
"unified_diff"
],
"title": "UpdateFileChange",
"type": "object"
}
]
},
"FileSystemPermissions": {
"properties": {
"read": {
"items": {
fix: use AbsolutePathBuf for permission profile file roots (#12970) ## Why `PermissionProfile` should describe filesystem roots as absolute paths at the type level. Using `PathBuf` in `FileSystemPermissions` made the shared type too permissive and blurred together three different deserialization cases: - skill metadata in `agents/openai.yaml`, where relative paths should resolve against the skill directory - app-server API payloads, where callers should have to send absolute paths - local tool-call payloads for commands like `shell_command` and `exec_command`, where `additional_permissions.file_system` may legitimately be relative to the command `workdir` This change tightens the shared model without regressing the existing local command flow. ## What Changed - changed `protocol::models::FileSystemPermissions` and the app-server `AdditionalFileSystemPermissions` mirror to use `AbsolutePathBuf` - wrapped skill metadata deserialization in `AbsolutePathBufGuard`, so relative permission roots in `agents/openai.yaml` resolve against the containing skill directory - kept app-server/API deserialization strict, so relative `additionalPermissions.fileSystem.*` paths are rejected at the boundary - restored cwd/workdir-relative deserialization for local tool-call payloads by parsing `shell`, `shell_command`, and `exec_command` arguments under an `AbsolutePathBufGuard` rooted at the resolved command working directory - simplified runtime additional-permission normalization so it only canonicalizes and deduplicates absolute roots instead of trying to recover relative ones later - updated the app-server schema fixtures, `app-server/README.md`, and the affected transport/TUI tests to match the final behavior
2026-02-27 09:42:52 -08:00
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": [
"array",
"null"
]
},
"write": {
"items": {
fix: use AbsolutePathBuf for permission profile file roots (#12970) ## Why `PermissionProfile` should describe filesystem roots as absolute paths at the type level. Using `PathBuf` in `FileSystemPermissions` made the shared type too permissive and blurred together three different deserialization cases: - skill metadata in `agents/openai.yaml`, where relative paths should resolve against the skill directory - app-server API payloads, where callers should have to send absolute paths - local tool-call payloads for commands like `shell_command` and `exec_command`, where `additional_permissions.file_system` may legitimately be relative to the command `workdir` This change tightens the shared model without regressing the existing local command flow. ## What Changed - changed `protocol::models::FileSystemPermissions` and the app-server `AdditionalFileSystemPermissions` mirror to use `AbsolutePathBuf` - wrapped skill metadata deserialization in `AbsolutePathBufGuard`, so relative permission roots in `agents/openai.yaml` resolve against the containing skill directory - kept app-server/API deserialization strict, so relative `additionalPermissions.fileSystem.*` paths are rejected at the boundary - restored cwd/workdir-relative deserialization for local tool-call payloads by parsing `shell`, `shell_command`, and `exec_command` arguments under an `AbsolutePathBufGuard` rooted at the resolved command working directory - simplified runtime additional-permission normalization so it only canonicalizes and deduplicates absolute roots instead of trying to recover relative ones later - updated the app-server schema fixtures, `app-server/README.md`, and the affected transport/TUI tests to match the final behavior
2026-02-27 09:42:52 -08:00
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": [
"array",
"null"
]
}
},
"type": "object"
},
"FunctionCallOutputBody": {
"anyOf": [
{
"type": "string"
},
{
"items": {
"$ref": "#/definitions/FunctionCallOutputContentItem"
},
"type": "array"
}
]
},
"FunctionCallOutputContentItem": {
"description": "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.",
"oneOf": [
{
"properties": {
"text": {
"type": "string"
},
"type": {
"enum": [
"input_text"
],
"title": "InputTextFunctionCallOutputContentItemType",
"type": "string"
}
},
"required": [
"text",
"type"
],
"title": "InputTextFunctionCallOutputContentItem",
"type": "object"
},
{
"properties": {
Add under-development original-resolution view_image support (#13050) ## Summary Add original-resolution support for `view_image` behind the under-development `view_image_original_resolution` feature flag. When the flag is enabled and the target model is `gpt-5.3-codex` or newer, `view_image` now preserves original PNG/JPEG/WebP bytes and sends `detail: "original"` to the Responses API instead of using the legacy resize/compress path. ## What changed - Added `view_image_original_resolution` as an under-development feature flag. - Added `ImageDetail` to the protocol models and support for serializing `detail: "original"` on tool-returned images. - Added `PromptImageMode::Original` to `codex-utils-image`. - Preserves original PNG/JPEG/WebP bytes. - Keeps legacy behavior for the resize path. - Updated `view_image` to: - use the shared `local_image_content_items_with_label_number(...)` helper in both code paths - select original-resolution mode only when: - the feature flag is enabled, and - the model slug parses as `gpt-5.3-codex` or newer - Kept local user image attachments on the existing resize path; this change is specific to `view_image`. - Updated history/image accounting so only `detail: "original"` images use the docs-based GPT-5 image cost calculation; legacy images still use the old fixed estimate. - Added JS REPL guidance, gated on the same feature flag, to prefer JPEG at 85% quality unless lossless is required, while still allowing other formats when explicitly requested. - Updated tests and helper code that construct `FunctionCallOutputContentItem::InputImage` to carry the new `detail` field. ## Behavior ### Feature off - `view_image` keeps the existing resize/re-encode behavior. - History estimation keeps the existing fixed-cost heuristic. ### Feature on + `gpt-5.3-codex+` - `view_image` sends original-resolution images with `detail: "original"`. - PNG/JPEG/WebP source bytes are preserved when possible. - History estimation uses the GPT-5 docs-based image-cost calculation for those `detail: "original"` images. #### [git stack](https://github.com/magus/git-stack-cli) - 👉 `1` https://github.com/openai/codex/pull/13050 - ⏳ `2` https://github.com/openai/codex/pull/13331 - ⏳ `3` https://github.com/openai/codex/pull/13049
2026-03-03 15:56:54 -08:00
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
]
},
"image_url": {
"type": "string"
},
"type": {
"enum": [
"input_image"
],
"title": "InputImageFunctionCallOutputContentItemType",
"type": "string"
}
},
"required": [
"image_url",
"type"
],
"title": "InputImageFunctionCallOutputContentItem",
"type": "object"
}
]
},
"FunctionCallOutputPayload": {
"description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`body` serializes directly as the wire value for `function_call_output.output`. `success` remains internal metadata for downstream handling.",
"properties": {
"body": {
"$ref": "#/definitions/FunctionCallOutputBody"
},
"success": {
"type": [
"boolean",
"null"
]
}
},
"required": [
"body"
],
"type": "object"
},
"GhostCommit": {
"description": "Details of a ghost commit created from a repository state.",
"properties": {
"id": {
"type": "string"
},
"parent": {
"type": [
"string",
"null"
]
},
"preexisting_untracked_dirs": {
"items": {
"type": "string"
},
"type": "array"
},
"preexisting_untracked_files": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"id",
"preexisting_untracked_dirs",
"preexisting_untracked_files"
],
"type": "object"
},
"HistoryEntry": {
"properties": {
"conversation_id": {
"type": "string"
},
"text": {
"type": "string"
},
"ts": {
"format": "uint64",
"minimum": 0.0,
"type": "integer"
}
},
"required": [
"conversation_id",
"text",
"ts"
],
"type": "object"
},
"HookEventName": {
"enum": [
"session_start",
"stop"
],
"type": "string"
},
"HookExecutionMode": {
"enum": [
"sync",
"async"
],
"type": "string"
},
"HookHandlerType": {
"enum": [
"command",
"prompt",
"agent"
],
"type": "string"
},
"HookOutputEntry": {
"properties": {
"kind": {
"$ref": "#/definitions/HookOutputEntryKind"
},
"text": {
"type": "string"
}
},
"required": [
"kind",
"text"
],
"type": "object"
},
"HookOutputEntryKind": {
"enum": [
"warning",
"stop",
"feedback",
"context",
"error"
],
"type": "string"
},
"HookRunStatus": {
"enum": [
"running",
"completed",
"failed",
"blocked",
"stopped"
],
"type": "string"
},
"HookRunSummary": {
"properties": {
"completed_at": {
"format": "int64",
"type": [
"integer",
"null"
]
},
"display_order": {
"format": "int64",
"type": "integer"
},
"duration_ms": {
"format": "int64",
"type": [
"integer",
"null"
]
},
"entries": {
"items": {
"$ref": "#/definitions/HookOutputEntry"
},
"type": "array"
},
"event_name": {
"$ref": "#/definitions/HookEventName"
},
"execution_mode": {
"$ref": "#/definitions/HookExecutionMode"
},
"handler_type": {
"$ref": "#/definitions/HookHandlerType"
},
"id": {
"type": "string"
},
"scope": {
"$ref": "#/definitions/HookScope"
},
"source_path": {
"type": "string"
},
"started_at": {
"format": "int64",
"type": "integer"
},
"status": {
"$ref": "#/definitions/HookRunStatus"
},
"status_message": {
"type": [
"string",
"null"
]
}
},
"required": [
"display_order",
"entries",
"event_name",
"execution_mode",
"handler_type",
"id",
"scope",
"source_path",
"started_at",
"status"
],
"type": "object"
},
"HookScope": {
"enum": [
"thread",
"turn"
],
"type": "string"
},
Add under-development original-resolution view_image support (#13050) ## Summary Add original-resolution support for `view_image` behind the under-development `view_image_original_resolution` feature flag. When the flag is enabled and the target model is `gpt-5.3-codex` or newer, `view_image` now preserves original PNG/JPEG/WebP bytes and sends `detail: "original"` to the Responses API instead of using the legacy resize/compress path. ## What changed - Added `view_image_original_resolution` as an under-development feature flag. - Added `ImageDetail` to the protocol models and support for serializing `detail: "original"` on tool-returned images. - Added `PromptImageMode::Original` to `codex-utils-image`. - Preserves original PNG/JPEG/WebP bytes. - Keeps legacy behavior for the resize path. - Updated `view_image` to: - use the shared `local_image_content_items_with_label_number(...)` helper in both code paths - select original-resolution mode only when: - the feature flag is enabled, and - the model slug parses as `gpt-5.3-codex` or newer - Kept local user image attachments on the existing resize path; this change is specific to `view_image`. - Updated history/image accounting so only `detail: "original"` images use the docs-based GPT-5 image cost calculation; legacy images still use the old fixed estimate. - Added JS REPL guidance, gated on the same feature flag, to prefer JPEG at 85% quality unless lossless is required, while still allowing other formats when explicitly requested. - Updated tests and helper code that construct `FunctionCallOutputContentItem::InputImage` to carry the new `detail` field. ## Behavior ### Feature off - `view_image` keeps the existing resize/re-encode behavior. - History estimation keeps the existing fixed-cost heuristic. ### Feature on + `gpt-5.3-codex+` - `view_image` sends original-resolution images with `detail: "original"`. - PNG/JPEG/WebP source bytes are preserved when possible. - History estimation uses the GPT-5 docs-based image-cost calculation for those `detail: "original"` images. #### [git stack](https://github.com/magus/git-stack-cli) - 👉 `1` https://github.com/openai/codex/pull/13050 - ⏳ `2` https://github.com/openai/codex/pull/13331 - ⏳ `3` https://github.com/openai/codex/pull/13049
2026-03-03 15:56:54 -08:00
"ImageDetail": {
"enum": [
"auto",
"low",
"high",
"original"
],
"type": "string"
},
"LocalShellAction": {
"oneOf": [
{
"properties": {
"command": {
"items": {
"type": "string"
},
"type": "array"
},
"env": {
"additionalProperties": {
"type": "string"
},
"type": [
"object",
"null"
]
},
"timeout_ms": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
},
"type": {
"enum": [
"exec"
],
"title": "ExecLocalShellActionType",
"type": "string"
},
"user": {
"type": [
"string",
"null"
]
},
"working_directory": {
"type": [
"string",
"null"
]
}
},
"required": [
"command",
"type"
],
"title": "ExecLocalShellAction",
"type": "object"
}
]
},
"LocalShellStatus": {
"enum": [
"completed",
"in_progress",
"incomplete"
],
"type": "string"
},
"MacOsAutomationPermission": {
"oneOf": [
{
"enum": [
"none",
"all"
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"bundle_ids": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"bundle_ids"
],
"title": "BundleIdsMacOsAutomationPermission",
"type": "object"
}
]
},
"MacOsContactsPermission": {
"enum": [
"none",
"read_only",
"read_write"
],
"type": "string"
},
"MacOsPreferencesPermission": {
"enum": [
"none",
"read_only",
"read_write"
],
"type": "string"
},
"MacOsSeatbeltProfileExtensions": {
"properties": {
"macos_accessibility": {
fix: accept two macOS automation input shapes for approval payload compatibility (#13683) ## Summary This PR: 1. fixes a deserialization mismatch for macOS automation permissions in approval payloads by making core parsing accept both supported wire shapes for bundle IDs. 2. added `#[serde(default)]` to `MacOsSeatbeltProfileExtensions` so omitted fields deserialize to secure defaults. ## Why this change is needed `MacOsAutomationPermission` uses `#[serde(try_from = "MacOsAutomationPermissionDe")]`, so deserialization is controlled by `MacOsAutomationPermissionDe`. After we aligned v2 `additionalPermissions.macos.automations` to the core shape, approval payloads started including `{ "bundle_ids": [...] }` in some paths. `MacOsAutomationPermissionDe` previously accepted only `"none" | "all"` or a plain array, so object-shaped bundle IDs failed with `data did not match any variant of untagged enum MacOsAutomationPermissionDe`. This change restores compatibility by accepting both forms while preserving existing normalization behavior (trim values and map empty bundle lists to `None`). ## Validation saw this error went away when running ``` cargo run -p codex-app-server-test-client -- \ --codex-bin ./target/debug/codex \ -c 'approval_policy="on-request"' \ -c 'features.shell_zsh_fork=true' \ -c 'zsh_path="/tmp/codex-zsh-fork/package/vendor/aarch64-apple-darwin/zsh/macos-15/zsh"' \ send-message-v2 --experimental-api \ 'Use $apple-notes and run scripts/notes_info now.' ``` : ``` Error: failed to deserialize ServerRequest from JSONRPCRequest Caused by: data did not match any variant of untagged enum MacOsAutomationPermissionDe ```
2026-03-05 22:02:33 -08:00
"default": false,
"type": "boolean"
},
"macos_automation": {
fix: accept two macOS automation input shapes for approval payload compatibility (#13683) ## Summary This PR: 1. fixes a deserialization mismatch for macOS automation permissions in approval payloads by making core parsing accept both supported wire shapes for bundle IDs. 2. added `#[serde(default)]` to `MacOsSeatbeltProfileExtensions` so omitted fields deserialize to secure defaults. ## Why this change is needed `MacOsAutomationPermission` uses `#[serde(try_from = "MacOsAutomationPermissionDe")]`, so deserialization is controlled by `MacOsAutomationPermissionDe`. After we aligned v2 `additionalPermissions.macos.automations` to the core shape, approval payloads started including `{ "bundle_ids": [...] }` in some paths. `MacOsAutomationPermissionDe` previously accepted only `"none" | "all"` or a plain array, so object-shaped bundle IDs failed with `data did not match any variant of untagged enum MacOsAutomationPermissionDe`. This change restores compatibility by accepting both forms while preserving existing normalization behavior (trim values and map empty bundle lists to `None`). ## Validation saw this error went away when running ``` cargo run -p codex-app-server-test-client -- \ --codex-bin ./target/debug/codex \ -c 'approval_policy="on-request"' \ -c 'features.shell_zsh_fork=true' \ -c 'zsh_path="/tmp/codex-zsh-fork/package/vendor/aarch64-apple-darwin/zsh/macos-15/zsh"' \ send-message-v2 --experimental-api \ 'Use $apple-notes and run scripts/notes_info now.' ``` : ``` Error: failed to deserialize ServerRequest from JSONRPCRequest Caused by: data did not match any variant of untagged enum MacOsAutomationPermissionDe ```
2026-03-05 22:02:33 -08:00
"allOf": [
{
"$ref": "#/definitions/MacOsAutomationPermission"
}
],
"default": "none"
},
"macos_calendar": {
fix: accept two macOS automation input shapes for approval payload compatibility (#13683) ## Summary This PR: 1. fixes a deserialization mismatch for macOS automation permissions in approval payloads by making core parsing accept both supported wire shapes for bundle IDs. 2. added `#[serde(default)]` to `MacOsSeatbeltProfileExtensions` so omitted fields deserialize to secure defaults. ## Why this change is needed `MacOsAutomationPermission` uses `#[serde(try_from = "MacOsAutomationPermissionDe")]`, so deserialization is controlled by `MacOsAutomationPermissionDe`. After we aligned v2 `additionalPermissions.macos.automations` to the core shape, approval payloads started including `{ "bundle_ids": [...] }` in some paths. `MacOsAutomationPermissionDe` previously accepted only `"none" | "all"` or a plain array, so object-shaped bundle IDs failed with `data did not match any variant of untagged enum MacOsAutomationPermissionDe`. This change restores compatibility by accepting both forms while preserving existing normalization behavior (trim values and map empty bundle lists to `None`). ## Validation saw this error went away when running ``` cargo run -p codex-app-server-test-client -- \ --codex-bin ./target/debug/codex \ -c 'approval_policy="on-request"' \ -c 'features.shell_zsh_fork=true' \ -c 'zsh_path="/tmp/codex-zsh-fork/package/vendor/aarch64-apple-darwin/zsh/macos-15/zsh"' \ send-message-v2 --experimental-api \ 'Use $apple-notes and run scripts/notes_info now.' ``` : ``` Error: failed to deserialize ServerRequest from JSONRPCRequest Caused by: data did not match any variant of untagged enum MacOsAutomationPermissionDe ```
2026-03-05 22:02:33 -08:00
"default": false,
"type": "boolean"
},
"macos_contacts": {
"allOf": [
{
"$ref": "#/definitions/MacOsContactsPermission"
}
],
"default": "none"
},
"macos_launch_services": {
"default": false,
"type": "boolean"
},
"macos_preferences": {
fix: accept two macOS automation input shapes for approval payload compatibility (#13683) ## Summary This PR: 1. fixes a deserialization mismatch for macOS automation permissions in approval payloads by making core parsing accept both supported wire shapes for bundle IDs. 2. added `#[serde(default)]` to `MacOsSeatbeltProfileExtensions` so omitted fields deserialize to secure defaults. ## Why this change is needed `MacOsAutomationPermission` uses `#[serde(try_from = "MacOsAutomationPermissionDe")]`, so deserialization is controlled by `MacOsAutomationPermissionDe`. After we aligned v2 `additionalPermissions.macos.automations` to the core shape, approval payloads started including `{ "bundle_ids": [...] }` in some paths. `MacOsAutomationPermissionDe` previously accepted only `"none" | "all"` or a plain array, so object-shaped bundle IDs failed with `data did not match any variant of untagged enum MacOsAutomationPermissionDe`. This change restores compatibility by accepting both forms while preserving existing normalization behavior (trim values and map empty bundle lists to `None`). ## Validation saw this error went away when running ``` cargo run -p codex-app-server-test-client -- \ --codex-bin ./target/debug/codex \ -c 'approval_policy="on-request"' \ -c 'features.shell_zsh_fork=true' \ -c 'zsh_path="/tmp/codex-zsh-fork/package/vendor/aarch64-apple-darwin/zsh/macos-15/zsh"' \ send-message-v2 --experimental-api \ 'Use $apple-notes and run scripts/notes_info now.' ``` : ``` Error: failed to deserialize ServerRequest from JSONRPCRequest Caused by: data did not match any variant of untagged enum MacOsAutomationPermissionDe ```
2026-03-05 22:02:33 -08:00
"allOf": [
{
"$ref": "#/definitions/MacOsPreferencesPermission"
}
],
"default": "read_only"
},
"macos_reminders": {
"default": false,
"type": "boolean"
}
},
"type": "object"
},
"McpAuthStatus": {
"enum": [
"unsupported",
"not_logged_in",
"bearer_token",
"o_auth"
],
"type": "string"
},
"McpInvocation": {
"properties": {
"arguments": {
"description": "Arguments to the tool call."
},
"server": {
"description": "Name of the MCP server as defined in the config.",
"type": "string"
},
"tool": {
"description": "Name of the tool as given by the MCP server.",
"type": "string"
}
},
"required": [
"server",
"tool"
],
"type": "object"
},
"McpStartupFailure": {
"properties": {
"error": {
"type": "string"
},
"server": {
"type": "string"
}
},
"required": [
"error",
"server"
],
"type": "object"
},
"McpStartupStatus": {
"oneOf": [
{
"properties": {
"state": {
"enum": [
"starting"
],
"type": "string"
}
},
"required": [
"state"
],
ignore v1 in JSON schema codegen (#12408) ## Why The generated unnamespaced JSON envelope schemas (`ClientRequest` and `ServerNotification`) still contained both v1 and v2 variants, which pulled legacy v1/core types and v2 types into the same `definitions` graph. That caused `schemars` to produce numeric suffix names (for example `AskForApproval2`, `ByteRange2`, `MessagePhase2`). This PR moves JSON codegen toward v2-only output while preserving the unnamespaced envelope artifacts, and avoids reintroducing numeric-suffix tolerance by removing the v1/internal-only variants that caused the collisions in those envelope schemas. ## What Changed - In `codex-rs/app-server-protocol/src/export.rs`, JSON generation now excludes v1 schema artifacts (`v1/*`) while continuing to emit unnamespaced/root JSON schemas and the JSON bundle. - Added a narrow JSON v1 allowlist (`JSON_V1_ALLOWLIST`) so `InitializeParams` and `InitializeResponse` are still emitted. - Added JSON-only post-processing for the mixed envelope schemas before collision checks run: - `ClientRequest`: strips v1 request variants from the generated `oneOf` using the temporary `V1_CLIENT_REQUEST_METHODS` list - `ServerNotification`: strips v1 notifications plus the internal-only `rawResponseItem/completed` notification using the temporary `EXCLUDED_SERVER_NOTIFICATION_METHODS_FOR_JSON` list - Added a temporary local-definition pruning pass for those envelope schemas so now-unreferenced v1/core definitions are removed from `definitions` after method filtering. - Updated the variant-title naming heuristic for single-property literal object variants to use the literal value (when available), avoiding collisions like multiple `state`-only variants all deriving the same title. - Collision handling remains fail-fast (no numeric suffix fallback map in this PR path). ## Verification - `just write-app-server-schema` --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/12408). * __->__ #12408 * #12406
2026-02-20 21:36:12 -08:00
"title": "StartingMcpStartupStatus",
"type": "object"
},
{
"properties": {
"state": {
"enum": [
"ready"
],
"type": "string"
}
},
"required": [
"state"
],
ignore v1 in JSON schema codegen (#12408) ## Why The generated unnamespaced JSON envelope schemas (`ClientRequest` and `ServerNotification`) still contained both v1 and v2 variants, which pulled legacy v1/core types and v2 types into the same `definitions` graph. That caused `schemars` to produce numeric suffix names (for example `AskForApproval2`, `ByteRange2`, `MessagePhase2`). This PR moves JSON codegen toward v2-only output while preserving the unnamespaced envelope artifacts, and avoids reintroducing numeric-suffix tolerance by removing the v1/internal-only variants that caused the collisions in those envelope schemas. ## What Changed - In `codex-rs/app-server-protocol/src/export.rs`, JSON generation now excludes v1 schema artifacts (`v1/*`) while continuing to emit unnamespaced/root JSON schemas and the JSON bundle. - Added a narrow JSON v1 allowlist (`JSON_V1_ALLOWLIST`) so `InitializeParams` and `InitializeResponse` are still emitted. - Added JSON-only post-processing for the mixed envelope schemas before collision checks run: - `ClientRequest`: strips v1 request variants from the generated `oneOf` using the temporary `V1_CLIENT_REQUEST_METHODS` list - `ServerNotification`: strips v1 notifications plus the internal-only `rawResponseItem/completed` notification using the temporary `EXCLUDED_SERVER_NOTIFICATION_METHODS_FOR_JSON` list - Added a temporary local-definition pruning pass for those envelope schemas so now-unreferenced v1/core definitions are removed from `definitions` after method filtering. - Updated the variant-title naming heuristic for single-property literal object variants to use the literal value (when available), avoiding collisions like multiple `state`-only variants all deriving the same title. - Collision handling remains fail-fast (no numeric suffix fallback map in this PR path). ## Verification - `just write-app-server-schema` --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/12408). * __->__ #12408 * #12406
2026-02-20 21:36:12 -08:00
"title": "ReadyMcpStartupStatus",
"type": "object"
},
{
"properties": {
"error": {
"type": "string"
},
"state": {
"enum": [
"failed"
],
"type": "string"
}
},
"required": [
"error",
"state"
],
"type": "object"
},
{
"properties": {
"state": {
"enum": [
"cancelled"
],
"type": "string"
}
},
"required": [
"state"
],
ignore v1 in JSON schema codegen (#12408) ## Why The generated unnamespaced JSON envelope schemas (`ClientRequest` and `ServerNotification`) still contained both v1 and v2 variants, which pulled legacy v1/core types and v2 types into the same `definitions` graph. That caused `schemars` to produce numeric suffix names (for example `AskForApproval2`, `ByteRange2`, `MessagePhase2`). This PR moves JSON codegen toward v2-only output while preserving the unnamespaced envelope artifacts, and avoids reintroducing numeric-suffix tolerance by removing the v1/internal-only variants that caused the collisions in those envelope schemas. ## What Changed - In `codex-rs/app-server-protocol/src/export.rs`, JSON generation now excludes v1 schema artifacts (`v1/*`) while continuing to emit unnamespaced/root JSON schemas and the JSON bundle. - Added a narrow JSON v1 allowlist (`JSON_V1_ALLOWLIST`) so `InitializeParams` and `InitializeResponse` are still emitted. - Added JSON-only post-processing for the mixed envelope schemas before collision checks run: - `ClientRequest`: strips v1 request variants from the generated `oneOf` using the temporary `V1_CLIENT_REQUEST_METHODS` list - `ServerNotification`: strips v1 notifications plus the internal-only `rawResponseItem/completed` notification using the temporary `EXCLUDED_SERVER_NOTIFICATION_METHODS_FOR_JSON` list - Added a temporary local-definition pruning pass for those envelope schemas so now-unreferenced v1/core definitions are removed from `definitions` after method filtering. - Updated the variant-title naming heuristic for single-property literal object variants to use the literal value (when available), avoiding collisions like multiple `state`-only variants all deriving the same title. - Collision handling remains fail-fast (no numeric suffix fallback map in this PR path). ## Verification - `just write-app-server-schema` --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/12408). * __->__ #12408 * #12406
2026-02-20 21:36:12 -08:00
"title": "CancelledMcpStartupStatus",
"type": "object"
}
]
},
"MessagePhase": {
"description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.",
"oneOf": [
{
"description": "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.",
"enum": [
"commentary"
],
"type": "string"
},
{
"description": "The assistant's terminal answer text for the current turn.",
"enum": [
"final_answer"
],
"type": "string"
}
]
},
"ModeKind": {
"description": "Initial collaboration mode to use when the TUI starts.",
"enum": [
"plan",
Cleanup collaboration mode variants (#10404) ## Summary This PR simplifies collaboration modes to the visible set `default | plan`, while preserving backward compatibility for older partners that may still send legacy mode names. Specifically: - Renames the old Code behavior to **Default**. - Keeps **Plan** as-is. - Removes **Custom** mode behavior (fallbacks now resolve to Default). - Keeps `PairProgramming` and `Execute` internally for compatibility plumbing, while removing them from schema/API and UI visibility. - Adds legacy input aliasing so older clients can still send old mode names. ## What Changed 1. Mode enum and compatibility - `ModeKind` now uses `Plan` + `Default` as active/public modes. - `ModeKind::Default` deserialization accepts legacy values: - `code` - `pair_programming` - `execute` - `custom` - `PairProgramming` and `Execute` variants remain in code but are hidden from protocol/schema generation. - `Custom` variant is removed; previous custom fallbacks now map to `Default`. 2. Collaboration presets and templates - Built-in presets now return only: - `Plan` - `Default` - Template rename: - `core/templates/collaboration_mode/code.md` -> `default.md` - `execute.md` and `pair_programming.md` remain on disk but are not surfaced in visible preset lists. 3. TUI updates - Updated user-facing naming and prompts from “Code” to “Default”. - Updated mode-cycle and indicator behavior to reflect only visible `Plan` and `Default`. - Updated corresponding tests and snapshots. 4. request_user_input behavior - `request_user_input` remains allowed only in `Plan` mode. - Rejection messaging now consistently treats non-plan modes as `Default`. 5. Schemas - Regenerated config and app-server schemas. - Public schema types now advertise mode values as: - `plan` - `default` ## Backward Compatibility Notes - Incoming legacy mode names (`code`, `pair_programming`, `execute`, `custom`) are accepted and coerced to `default`. - Outgoing/public schema surfaces intentionally expose only `plan | default`. - This allows tolerant ingestion of older partner payloads while standardizing new integrations on the reduced mode set. ## Codex author `codex fork 019c1fae-693b-7840-b16e-9ad38ea0bd00`
2026-02-03 09:23:53 -08:00
"default"
],
"type": "string"
},
"ModelRerouteReason": {
"enum": [
"high_risk_cyber_activity"
],
"type": "string"
},
"NetworkAccess": {
"description": "Represents whether outbound network access is available to the agent.",
"enum": [
"restricted",
"enabled"
],
"type": "string"
},
"NetworkApprovalContext": {
"properties": {
"host": {
"type": "string"
},
"protocol": {
"$ref": "#/definitions/NetworkApprovalProtocol"
}
},
"required": [
"host",
"protocol"
],
"type": "object"
},
"NetworkApprovalProtocol": {
"enum": [
"http",
"https",
"socks5_tcp",
"socks5_udp"
],
"type": "string"
},
"NetworkPermissions": {
"properties": {
"enabled": {
"type": [
"boolean",
"null"
]
}
},
"type": "object"
},
"NetworkPolicyAmendment": {
"properties": {
"action": {
"$ref": "#/definitions/NetworkPolicyRuleAction"
},
"host": {
"type": "string"
}
},
"required": [
"action",
"host"
],
"type": "object"
},
"NetworkPolicyRuleAction": {
"enum": [
"allow",
"deny"
],
"type": "string"
},
"ParsedCommand": {
"oneOf": [
{
"properties": {
"cmd": {
"type": "string"
},
"name": {
"type": "string"
},
"path": {
"description": "(Best effort) Path to the file being read by the command. When possible, this is an absolute path, though when relative, it should be resolved against the `cwd`` that will be used to run the command to derive the absolute path.",
"type": "string"
},
"type": {
"enum": [
"read"
],
"title": "ReadParsedCommandType",
"type": "string"
}
},
"required": [
"cmd",
"name",
"path",
"type"
],
"title": "ReadParsedCommand",
"type": "object"
},
{
"properties": {
"cmd": {
"type": "string"
},
"path": {
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"list_files"
],
"title": "ListFilesParsedCommandType",
"type": "string"
}
},
"required": [
"cmd",
"type"
],
"title": "ListFilesParsedCommand",
"type": "object"
},
{
"properties": {
"cmd": {
"type": "string"
},
"path": {
"type": [
"string",
"null"
]
},
"query": {
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"search"
],
"title": "SearchParsedCommandType",
"type": "string"
}
},
"required": [
"cmd",
"type"
],
"title": "SearchParsedCommand",
"type": "object"
},
{
"properties": {
"cmd": {
"type": "string"
},
"type": {
"enum": [
"unknown"
],
"title": "UnknownParsedCommandType",
"type": "string"
}
},
"required": [
"cmd",
"type"
],
"title": "UnknownParsedCommand",
"type": "object"
}
]
},
feat(app-server): experimental flag to persist extended history (#11227) This PR adds an experimental `persist_extended_history` bool flag to app-server thread APIs so rollout logs can retain a richer set of EventMsgs for non-lossy Thread > Turn > ThreadItems reconstruction (i.e. on `thread/resume`). ### Motivation Today, our rollout recorder only persists a small subset (e.g. user message, reasoning, assistant message) of `EventMsg` types, dropping a good number (like command exec, file change, etc.) that are important for reconstructing full item history for `thread/resume`, `thread/read`, and `thread/fork`. Some clients want to be able to resume a thread without lossiness. This lossiness is primarily a UI thing, since what the model sees are `ResponseItem` and not `EventMsg`. ### Approach This change introduces an opt-in `persist_full_history` flag to preserve those events when you start/resume/fork a thread (defaults to `false`). This is done by adding an `EventPersistenceMode` to the rollout recorder: - `Limited` (existing behavior, default) - `Extended` (new opt-in behavior) In `Extended` mode, persist additional `EventMsg` variants needed for non-lossy app-server `ThreadItem` reconstruction. We now store the following ThreadItems that we didn't before: - web search - command execution - patch/file changes - MCP tool calls - image view calls - collab tool outcomes - context compaction - review mode enter/exit For **command executions** in particular, we truncate the output using the existing `truncate_text` from core to store an upper bound of 10,000 bytes, which is also the default value for truncating tool outputs shown to the model. This keeps the size of the rollout file and command execution items returned over the wire reasonable. And we also persist `EventMsg::Error` which we can now map back to the Turn's status and populates the Turn's error metadata. #### Updates to EventMsgs To truly make `thread/resume` non-lossy, we also needed to persist the `status` on `EventMsg::CommandExecutionEndEvent` and `EventMsg::PatchApplyEndEvent`. Previously it was not obvious whether a command failed or was declined (similar for apply_patch). These EventMsgs were never persisted before so I made it a required field.
2026-02-12 11:34:22 -08:00
"PatchApplyStatus": {
"enum": [
"completed",
"failed",
"declined"
],
"type": "string"
},
"PermissionProfile": {
"properties": {
"file_system": {
"anyOf": [
{
"$ref": "#/definitions/FileSystemPermissions"
},
{
"type": "null"
}
]
},
"macos": {
"anyOf": [
{
"$ref": "#/definitions/MacOsSeatbeltProfileExtensions"
},
{
"type": "null"
}
]
},
"network": {
"anyOf": [
{
"$ref": "#/definitions/NetworkPermissions"
},
{
"type": "null"
}
]
}
},
"type": "object"
},
"PlanItemArg": {
"additionalProperties": false,
"properties": {
"status": {
"$ref": "#/definitions/StepStatus"
},
"step": {
"type": "string"
}
},
"required": [
"status",
"step"
],
"type": "object"
},
"PlanType": {
"enum": [
"free",
"go",
"plus",
"pro",
"team",
"business",
"enterprise",
"edu",
"unknown"
],
"type": "string"
},
"RateLimitSnapshot": {
"properties": {
"credits": {
"anyOf": [
{
"$ref": "#/definitions/CreditsSnapshot"
},
{
"type": "null"
}
]
},
"limit_id": {
"type": [
"string",
"null"
]
},
"limit_name": {
"type": [
"string",
"null"
]
},
"plan_type": {
"anyOf": [
{
"$ref": "#/definitions/PlanType"
},
{
"type": "null"
}
]
},
"primary": {
"anyOf": [
{
"$ref": "#/definitions/RateLimitWindow"
},
{
"type": "null"
}
]
},
"secondary": {
"anyOf": [
{
"$ref": "#/definitions/RateLimitWindow"
},
{
"type": "null"
}
]
}
},
"type": "object"
},
"RateLimitWindow": {
"properties": {
"resets_at": {
"description": "Unix timestamp (seconds since epoch) when the window resets.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"used_percent": {
"description": "Percentage (0-100) of the window that has been consumed.",
"format": "double",
"type": "number"
},
"window_minutes": {
"description": "Rolling window duration, in minutes.",
"format": "int64",
"type": [
"integer",
"null"
]
}
},
"required": [
"used_percent"
],
"type": "object"
},
feat: make sandbox read access configurable with `ReadOnlyAccess` (#11387) `SandboxPolicy::ReadOnly` previously implied broad read access and could not express a narrower read surface. This change introduces an explicit read-access model so we can support user-configurable read restrictions in follow-up work, while preserving current behavior today. It also ensures unsupported backends fail closed for restricted-read policies instead of silently granting broader access than intended. ## What - Added `ReadOnlyAccess` in protocol with: - `Restricted { include_platform_defaults, readable_roots }` - `FullAccess` - Updated `SandboxPolicy` to carry read-access configuration: - `ReadOnly { access: ReadOnlyAccess }` - `WorkspaceWrite { ..., read_only_access: ReadOnlyAccess }` - Preserved existing behavior by defaulting current construction paths to `ReadOnlyAccess::FullAccess`. - Threaded the new fields through sandbox policy consumers and call sites across `core`, `tui`, `linux-sandbox`, `windows-sandbox`, and related tests. - Updated Seatbelt policy generation to honor restricted read roots by emitting scoped read rules when full read access is not granted. - Added fail-closed behavior on Linux and Windows backends when restricted read access is requested but not yet implemented there (`UnsupportedOperation`). - Regenerated app-server protocol schema and TypeScript artifacts, including `ReadOnlyAccess`. ## Compatibility / rollout - Runtime behavior remains unchanged by default (`FullAccess`). - API/schema changes are in place so future config wiring can enable restricted read access without another policy-shape migration.
2026-02-11 18:31:14 -08:00
"ReadOnlyAccess": {
"description": "Determines how read-only file access is granted inside a restricted sandbox.",
"oneOf": [
{
"description": "Restrict reads to an explicit set of roots.\n\nWhen `include_platform_defaults` is `true`, platform defaults required for basic execution are included in addition to `readable_roots`.",
"properties": {
"include_platform_defaults": {
"default": true,
"description": "Include built-in platform read roots required for basic process execution.",
"type": "boolean"
},
"readable_roots": {
"description": "Additional absolute roots that should be readable.",
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": "array"
},
"type": {
"enum": [
"restricted"
],
"title": "RestrictedReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "RestrictedReadOnlyAccess",
"type": "object"
},
{
"description": "Allow unrestricted file reads.",
"properties": {
"type": {
"enum": [
"full-access"
],
"title": "FullAccessReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "FullAccessReadOnlyAccess",
"type": "object"
}
]
},
"RealtimeAudioFrame": {
"properties": {
"data": {
"type": "string"
},
"num_channels": {
"format": "uint16",
"minimum": 0.0,
"type": "integer"
},
"sample_rate": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"samples_per_channel": {
"format": "uint32",
"minimum": 0.0,
"type": [
"integer",
"null"
]
}
},
"required": [
"data",
"num_channels",
"sample_rate"
],
"type": "object"
},
"RealtimeEvent": {
"oneOf": [
{
"additionalProperties": false,
"properties": {
"SessionUpdated": {
"properties": {
"instructions": {
"type": [
"string",
"null"
]
},
"session_id": {
"type": "string"
}
},
"required": [
"session_id"
],
"type": "object"
}
},
"required": [
"SessionUpdated"
],
"title": "SessionUpdatedRealtimeEvent",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"InputTranscriptDelta": {
"$ref": "#/definitions/RealtimeTranscriptDelta"
}
},
"required": [
"InputTranscriptDelta"
],
"title": "InputTranscriptDeltaRealtimeEvent",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"OutputTranscriptDelta": {
"$ref": "#/definitions/RealtimeTranscriptDelta"
}
},
"required": [
"OutputTranscriptDelta"
],
"title": "OutputTranscriptDeltaRealtimeEvent",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"AudioOut": {
"$ref": "#/definitions/RealtimeAudioFrame"
}
},
"required": [
"AudioOut"
],
"title": "AudioOutRealtimeEvent",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"ConversationItemAdded": true
},
"required": [
"ConversationItemAdded"
],
"title": "ConversationItemAddedRealtimeEvent",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"ConversationItemDone": {
"properties": {
"item_id": {
"type": "string"
}
},
"required": [
"item_id"
],
"type": "object"
}
},
"required": [
"ConversationItemDone"
],
"title": "ConversationItemDoneRealtimeEvent",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"HandoffRequested": {
"$ref": "#/definitions/RealtimeHandoffRequested"
}
},
"required": [
"HandoffRequested"
],
"title": "HandoffRequestedRealtimeEvent",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"Error": {
"type": "string"
}
},
"required": [
"Error"
],
"title": "ErrorRealtimeEvent",
"type": "object"
}
]
},
"RealtimeHandoffRequested": {
"properties": {
"active_transcript": {
"items": {
"$ref": "#/definitions/RealtimeTranscriptEntry"
},
"type": "array"
},
"handoff_id": {
"type": "string"
},
"input_transcript": {
"type": "string"
},
"item_id": {
"type": "string"
}
},
"required": [
"active_transcript",
"handoff_id",
"input_transcript",
"item_id"
],
"type": "object"
},
"RealtimeTranscriptDelta": {
"properties": {
"delta": {
"type": "string"
}
},
"required": [
"delta"
],
"type": "object"
},
"RealtimeTranscriptEntry": {
"properties": {
"role": {
"type": "string"
},
"text": {
"type": "string"
}
},
"required": [
"role",
"text"
],
"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"
},
"ReasoningItemContent": {
"oneOf": [
{
"properties": {
"text": {
"type": "string"
},
"type": {
"enum": [
"reasoning_text"
],
"title": "ReasoningTextReasoningItemContentType",
"type": "string"
}
},
"required": [
"text",
"type"
],
"title": "ReasoningTextReasoningItemContent",
"type": "object"
},
{
"properties": {
"text": {
"type": "string"
},
"type": {
"enum": [
"text"
],
"title": "TextReasoningItemContentType",
"type": "string"
}
},
"required": [
"text",
"type"
],
"title": "TextReasoningItemContent",
"type": "object"
}
]
},
"ReasoningItemReasoningSummary": {
"oneOf": [
{
"properties": {
"text": {
"type": "string"
},
"type": {
"enum": [
"summary_text"
],
"title": "SummaryTextReasoningItemReasoningSummaryType",
"type": "string"
}
},
"required": [
"text",
"type"
],
"title": "SummaryTextReasoningItemReasoningSummary",
"type": "object"
}
]
},
feat: add Reject approval policy with granular prompt rejection controls (#12087) ## Why We need a way to auto-reject specific approval prompt categories without switching all approvals off. The goal is to let users independently control: - sandbox escalation approvals, - execpolicy `prompt` rule approvals, - MCP elicitation prompts. ## What changed - Added a new primary approval mode in `protocol/src/protocol.rs`: ```rust pub enum AskForApproval { // ... Reject(RejectConfig), // ... } pub struct RejectConfig { pub sandbox_approval: bool, pub rules: bool, pub mcp_elicitations: bool, } ``` - Wired `RejectConfig` semantics through approval paths in `core`: - `core/src/exec_policy.rs` - rejects rule-driven prompts when `rules = true` - rejects sandbox/escalation prompts when `sandbox_approval = true` - preserves rule priority when both rule and sandbox prompt conditions are present - `core/src/tools/sandboxing.rs` - applies `sandbox_approval` to default exec approval decisions and sandbox-failure retry gating - `core/src/safety.rs` - keeps `Reject { all false }` behavior aligned with `OnRequest` for patch safety - rejects out-of-root patch approvals when `sandbox_approval = true` - `core/src/mcp_connection_manager.rs` - auto-declines MCP elicitations when `mcp_elicitations = true` - Ensured approval policy used by MCP elicitation flow stays in sync with constrained session policy updates. - Updated app-server v2 conversions and generated schema/TypeScript artifacts for the new `Reject` shape. ## Verification Added focused unit coverage for the new behavior in: - `core/src/exec_policy.rs` - `core/src/tools/sandboxing.rs` - `core/src/mcp_connection_manager.rs` - `core/src/safety.rs` - `core/src/tools/runtimes/apply_patch.rs` Key cases covered include rule-vs-sandbox prompt precedence, MCP auto-decline behavior, and patch/sandbox retry behavior under `RejectConfig`.
2026-02-19 11:41:49 -08:00
"RejectConfig": {
"properties": {
"mcp_elicitations": {
"description": "Reject MCP elicitation prompts.",
"type": "boolean"
},
"request_permissions": {
"default": false,
"description": "Reject approval prompts related to built-in permission requests.",
"type": "boolean"
},
feat: add Reject approval policy with granular prompt rejection controls (#12087) ## Why We need a way to auto-reject specific approval prompt categories without switching all approvals off. The goal is to let users independently control: - sandbox escalation approvals, - execpolicy `prompt` rule approvals, - MCP elicitation prompts. ## What changed - Added a new primary approval mode in `protocol/src/protocol.rs`: ```rust pub enum AskForApproval { // ... Reject(RejectConfig), // ... } pub struct RejectConfig { pub sandbox_approval: bool, pub rules: bool, pub mcp_elicitations: bool, } ``` - Wired `RejectConfig` semantics through approval paths in `core`: - `core/src/exec_policy.rs` - rejects rule-driven prompts when `rules = true` - rejects sandbox/escalation prompts when `sandbox_approval = true` - preserves rule priority when both rule and sandbox prompt conditions are present - `core/src/tools/sandboxing.rs` - applies `sandbox_approval` to default exec approval decisions and sandbox-failure retry gating - `core/src/safety.rs` - keeps `Reject { all false }` behavior aligned with `OnRequest` for patch safety - rejects out-of-root patch approvals when `sandbox_approval = true` - `core/src/mcp_connection_manager.rs` - auto-declines MCP elicitations when `mcp_elicitations = true` - Ensured approval policy used by MCP elicitation flow stays in sync with constrained session policy updates. - Updated app-server v2 conversions and generated schema/TypeScript artifacts for the new `Reject` shape. ## Verification Added focused unit coverage for the new behavior in: - `core/src/exec_policy.rs` - `core/src/tools/sandboxing.rs` - `core/src/mcp_connection_manager.rs` - `core/src/safety.rs` - `core/src/tools/runtimes/apply_patch.rs` Key cases covered include rule-vs-sandbox prompt precedence, MCP auto-decline behavior, and patch/sandbox retry behavior under `RejectConfig`.
2026-02-19 11:41:49 -08:00
"rules": {
"description": "Reject prompts triggered by execpolicy `prompt` rules.",
"type": "boolean"
},
"sandbox_approval": {
"description": "Reject approval prompts related to sandbox escalation.",
"type": "boolean"
},
"skill_approval": {
"default": false,
"description": "Reject approval prompts triggered by skill script execution.",
"type": "boolean"
feat: add Reject approval policy with granular prompt rejection controls (#12087) ## Why We need a way to auto-reject specific approval prompt categories without switching all approvals off. The goal is to let users independently control: - sandbox escalation approvals, - execpolicy `prompt` rule approvals, - MCP elicitation prompts. ## What changed - Added a new primary approval mode in `protocol/src/protocol.rs`: ```rust pub enum AskForApproval { // ... Reject(RejectConfig), // ... } pub struct RejectConfig { pub sandbox_approval: bool, pub rules: bool, pub mcp_elicitations: bool, } ``` - Wired `RejectConfig` semantics through approval paths in `core`: - `core/src/exec_policy.rs` - rejects rule-driven prompts when `rules = true` - rejects sandbox/escalation prompts when `sandbox_approval = true` - preserves rule priority when both rule and sandbox prompt conditions are present - `core/src/tools/sandboxing.rs` - applies `sandbox_approval` to default exec approval decisions and sandbox-failure retry gating - `core/src/safety.rs` - keeps `Reject { all false }` behavior aligned with `OnRequest` for patch safety - rejects out-of-root patch approvals when `sandbox_approval = true` - `core/src/mcp_connection_manager.rs` - auto-declines MCP elicitations when `mcp_elicitations = true` - Ensured approval policy used by MCP elicitation flow stays in sync with constrained session policy updates. - Updated app-server v2 conversions and generated schema/TypeScript artifacts for the new `Reject` shape. ## Verification Added focused unit coverage for the new behavior in: - `core/src/exec_policy.rs` - `core/src/tools/sandboxing.rs` - `core/src/mcp_connection_manager.rs` - `core/src/safety.rs` - `core/src/tools/runtimes/apply_patch.rs` Key cases covered include rule-vs-sandbox prompt precedence, MCP auto-decline behavior, and patch/sandbox retry behavior under `RejectConfig`.
2026-02-19 11:41:49 -08:00
}
},
"required": [
"mcp_elicitations",
"rules",
"sandbox_approval"
],
"type": "object"
},
"RemoteSkillSummary": {
"properties": {
"description": {
"type": "string"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
}
},
"required": [
"description",
"id",
"name"
],
"type": "object"
},
"RequestId": {
"anyOf": [
{
"type": "string"
},
{
"format": "int64",
"type": "integer"
}
feat: replace custom mcp-types crate with equivalents from rmcp (#10349) We started working with MCP in Codex before https://crates.io/crates/rmcp was mature, so we had our own crate for MCP types that was generated from the MCP schema: https://github.com/openai/codex/blob/8b95d3e082376f4cb23e92641705a22afb28a9da/codex-rs/mcp-types/README.md Now that `rmcp` is more mature, it makes more sense to use their MCP types in Rust, as they handle details (like the `_meta` field) that our custom version ignored. Though one advantage that our custom types had is that our generated types implemented `JsonSchema` and `ts_rs::TS`, whereas the types in `rmcp` do not. As such, part of the work of this PR is leveraging the adapters between `rmcp` types and the serializable types that are API for us (app server and MCP) introduced in #10356. Note this PR results in a number of changes to `codex-rs/app-server-protocol/schema`, which merit special attention during review. We must ensure that these changes are still backwards-compatible, which is possible because we have: ```diff - export type CallToolResult = { content: Array<ContentBlock>, isError?: boolean, structuredContent?: JsonValue, }; + export type CallToolResult = { content: Array<JsonValue>, structuredContent?: JsonValue, isError?: boolean, _meta?: JsonValue, }; ``` so `ContentBlock` has been replaced with the more general `JsonValue`. Note that `ContentBlock` was defined as: ```typescript export type ContentBlock = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource; ``` so the deletion of those individual variants should not be a cause of great concern. Similarly, we have the following change in `codex-rs/app-server-protocol/schema/typescript/Tool.ts`: ``` - export type Tool = { annotations?: ToolAnnotations, description?: string, inputSchema: ToolInputSchema, name: string, outputSchema?: ToolOutputSchema, title?: string, }; + export type Tool = { name: string, title?: string, description?: string, inputSchema: JsonValue, outputSchema?: JsonValue, annotations?: JsonValue, icons?: Array<JsonValue>, _meta?: JsonValue, }; ``` so: - `annotations?: ToolAnnotations` ➡️ `JsonValue` - `inputSchema: ToolInputSchema` ➡️ `JsonValue` - `outputSchema?: ToolOutputSchema` ➡️ `JsonValue` and two new fields: `icons?: Array<JsonValue>, _meta?: JsonValue` --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/10349). * #10357 * __->__ #10349 * #10356
2026-02-02 17:41:55 -08:00
],
"description": "ID of a request, which can be either a string or an integer."
},
"RequestUserInputQuestion": {
"properties": {
"header": {
"type": "string"
},
"id": {
"type": "string"
},
"isOther": {
"default": false,
"type": "boolean"
},
"isSecret": {
"default": false,
"type": "boolean"
},
"options": {
"items": {
"$ref": "#/definitions/RequestUserInputQuestionOption"
},
"type": [
"array",
"null"
]
},
"question": {
"type": "string"
}
},
"required": [
"header",
"id",
"question"
],
"type": "object"
},
"RequestUserInputQuestionOption": {
"properties": {
"description": {
"type": "string"
},
"label": {
"type": "string"
}
},
"required": [
"description",
"label"
],
"type": "object"
},
"Resource": {
"description": "A known resource that the server is capable of reading.",
"properties": {
feat: replace custom mcp-types crate with equivalents from rmcp (#10349) We started working with MCP in Codex before https://crates.io/crates/rmcp was mature, so we had our own crate for MCP types that was generated from the MCP schema: https://github.com/openai/codex/blob/8b95d3e082376f4cb23e92641705a22afb28a9da/codex-rs/mcp-types/README.md Now that `rmcp` is more mature, it makes more sense to use their MCP types in Rust, as they handle details (like the `_meta` field) that our custom version ignored. Though one advantage that our custom types had is that our generated types implemented `JsonSchema` and `ts_rs::TS`, whereas the types in `rmcp` do not. As such, part of the work of this PR is leveraging the adapters between `rmcp` types and the serializable types that are API for us (app server and MCP) introduced in #10356. Note this PR results in a number of changes to `codex-rs/app-server-protocol/schema`, which merit special attention during review. We must ensure that these changes are still backwards-compatible, which is possible because we have: ```diff - export type CallToolResult = { content: Array<ContentBlock>, isError?: boolean, structuredContent?: JsonValue, }; + export type CallToolResult = { content: Array<JsonValue>, structuredContent?: JsonValue, isError?: boolean, _meta?: JsonValue, }; ``` so `ContentBlock` has been replaced with the more general `JsonValue`. Note that `ContentBlock` was defined as: ```typescript export type ContentBlock = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource; ``` so the deletion of those individual variants should not be a cause of great concern. Similarly, we have the following change in `codex-rs/app-server-protocol/schema/typescript/Tool.ts`: ``` - export type Tool = { annotations?: ToolAnnotations, description?: string, inputSchema: ToolInputSchema, name: string, outputSchema?: ToolOutputSchema, title?: string, }; + export type Tool = { name: string, title?: string, description?: string, inputSchema: JsonValue, outputSchema?: JsonValue, annotations?: JsonValue, icons?: Array<JsonValue>, _meta?: JsonValue, }; ``` so: - `annotations?: ToolAnnotations` ➡️ `JsonValue` - `inputSchema: ToolInputSchema` ➡️ `JsonValue` - `outputSchema?: ToolOutputSchema` ➡️ `JsonValue` and two new fields: `icons?: Array<JsonValue>, _meta?: JsonValue` --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/10349). * #10357 * __->__ #10349 * #10356
2026-02-02 17:41:55 -08:00
"_meta": true,
"annotations": true,
"description": {
"type": [
"string",
"null"
]
},
feat: replace custom mcp-types crate with equivalents from rmcp (#10349) We started working with MCP in Codex before https://crates.io/crates/rmcp was mature, so we had our own crate for MCP types that was generated from the MCP schema: https://github.com/openai/codex/blob/8b95d3e082376f4cb23e92641705a22afb28a9da/codex-rs/mcp-types/README.md Now that `rmcp` is more mature, it makes more sense to use their MCP types in Rust, as they handle details (like the `_meta` field) that our custom version ignored. Though one advantage that our custom types had is that our generated types implemented `JsonSchema` and `ts_rs::TS`, whereas the types in `rmcp` do not. As such, part of the work of this PR is leveraging the adapters between `rmcp` types and the serializable types that are API for us (app server and MCP) introduced in #10356. Note this PR results in a number of changes to `codex-rs/app-server-protocol/schema`, which merit special attention during review. We must ensure that these changes are still backwards-compatible, which is possible because we have: ```diff - export type CallToolResult = { content: Array<ContentBlock>, isError?: boolean, structuredContent?: JsonValue, }; + export type CallToolResult = { content: Array<JsonValue>, structuredContent?: JsonValue, isError?: boolean, _meta?: JsonValue, }; ``` so `ContentBlock` has been replaced with the more general `JsonValue`. Note that `ContentBlock` was defined as: ```typescript export type ContentBlock = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource; ``` so the deletion of those individual variants should not be a cause of great concern. Similarly, we have the following change in `codex-rs/app-server-protocol/schema/typescript/Tool.ts`: ``` - export type Tool = { annotations?: ToolAnnotations, description?: string, inputSchema: ToolInputSchema, name: string, outputSchema?: ToolOutputSchema, title?: string, }; + export type Tool = { name: string, title?: string, description?: string, inputSchema: JsonValue, outputSchema?: JsonValue, annotations?: JsonValue, icons?: Array<JsonValue>, _meta?: JsonValue, }; ``` so: - `annotations?: ToolAnnotations` ➡️ `JsonValue` - `inputSchema: ToolInputSchema` ➡️ `JsonValue` - `outputSchema?: ToolOutputSchema` ➡️ `JsonValue` and two new fields: `icons?: Array<JsonValue>, _meta?: JsonValue` --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/10349). * #10357 * __->__ #10349 * #10356
2026-02-02 17:41:55 -08:00
"icons": {
"items": true,
"type": [
feat: replace custom mcp-types crate with equivalents from rmcp (#10349) We started working with MCP in Codex before https://crates.io/crates/rmcp was mature, so we had our own crate for MCP types that was generated from the MCP schema: https://github.com/openai/codex/blob/8b95d3e082376f4cb23e92641705a22afb28a9da/codex-rs/mcp-types/README.md Now that `rmcp` is more mature, it makes more sense to use their MCP types in Rust, as they handle details (like the `_meta` field) that our custom version ignored. Though one advantage that our custom types had is that our generated types implemented `JsonSchema` and `ts_rs::TS`, whereas the types in `rmcp` do not. As such, part of the work of this PR is leveraging the adapters between `rmcp` types and the serializable types that are API for us (app server and MCP) introduced in #10356. Note this PR results in a number of changes to `codex-rs/app-server-protocol/schema`, which merit special attention during review. We must ensure that these changes are still backwards-compatible, which is possible because we have: ```diff - export type CallToolResult = { content: Array<ContentBlock>, isError?: boolean, structuredContent?: JsonValue, }; + export type CallToolResult = { content: Array<JsonValue>, structuredContent?: JsonValue, isError?: boolean, _meta?: JsonValue, }; ``` so `ContentBlock` has been replaced with the more general `JsonValue`. Note that `ContentBlock` was defined as: ```typescript export type ContentBlock = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource; ``` so the deletion of those individual variants should not be a cause of great concern. Similarly, we have the following change in `codex-rs/app-server-protocol/schema/typescript/Tool.ts`: ``` - export type Tool = { annotations?: ToolAnnotations, description?: string, inputSchema: ToolInputSchema, name: string, outputSchema?: ToolOutputSchema, title?: string, }; + export type Tool = { name: string, title?: string, description?: string, inputSchema: JsonValue, outputSchema?: JsonValue, annotations?: JsonValue, icons?: Array<JsonValue>, _meta?: JsonValue, }; ``` so: - `annotations?: ToolAnnotations` ➡️ `JsonValue` - `inputSchema: ToolInputSchema` ➡️ `JsonValue` - `outputSchema?: ToolOutputSchema` ➡️ `JsonValue` and two new fields: `icons?: Array<JsonValue>, _meta?: JsonValue` --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/10349). * #10357 * __->__ #10349 * #10356
2026-02-02 17:41:55 -08:00
"array",
"null"
]
},
"mimeType": {
"type": [
"string",
"null"
]
},
"name": {
"type": "string"
},
"size": {
"format": "int64",
"type": [
"integer",
"null"
]
},
"title": {
"type": [
"string",
"null"
]
},
"uri": {
"type": "string"
}
},
"required": [
"name",
"uri"
],
"type": "object"
},
"ResourceTemplate": {
"description": "A template description for resources available on the server.",
"properties": {
feat: replace custom mcp-types crate with equivalents from rmcp (#10349) We started working with MCP in Codex before https://crates.io/crates/rmcp was mature, so we had our own crate for MCP types that was generated from the MCP schema: https://github.com/openai/codex/blob/8b95d3e082376f4cb23e92641705a22afb28a9da/codex-rs/mcp-types/README.md Now that `rmcp` is more mature, it makes more sense to use their MCP types in Rust, as they handle details (like the `_meta` field) that our custom version ignored. Though one advantage that our custom types had is that our generated types implemented `JsonSchema` and `ts_rs::TS`, whereas the types in `rmcp` do not. As such, part of the work of this PR is leveraging the adapters between `rmcp` types and the serializable types that are API for us (app server and MCP) introduced in #10356. Note this PR results in a number of changes to `codex-rs/app-server-protocol/schema`, which merit special attention during review. We must ensure that these changes are still backwards-compatible, which is possible because we have: ```diff - export type CallToolResult = { content: Array<ContentBlock>, isError?: boolean, structuredContent?: JsonValue, }; + export type CallToolResult = { content: Array<JsonValue>, structuredContent?: JsonValue, isError?: boolean, _meta?: JsonValue, }; ``` so `ContentBlock` has been replaced with the more general `JsonValue`. Note that `ContentBlock` was defined as: ```typescript export type ContentBlock = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource; ``` so the deletion of those individual variants should not be a cause of great concern. Similarly, we have the following change in `codex-rs/app-server-protocol/schema/typescript/Tool.ts`: ``` - export type Tool = { annotations?: ToolAnnotations, description?: string, inputSchema: ToolInputSchema, name: string, outputSchema?: ToolOutputSchema, title?: string, }; + export type Tool = { name: string, title?: string, description?: string, inputSchema: JsonValue, outputSchema?: JsonValue, annotations?: JsonValue, icons?: Array<JsonValue>, _meta?: JsonValue, }; ``` so: - `annotations?: ToolAnnotations` ➡️ `JsonValue` - `inputSchema: ToolInputSchema` ➡️ `JsonValue` - `outputSchema?: ToolOutputSchema` ➡️ `JsonValue` and two new fields: `icons?: Array<JsonValue>, _meta?: JsonValue` --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/10349). * #10357 * __->__ #10349 * #10356
2026-02-02 17:41:55 -08:00
"annotations": true,
"description": {
"type": [
"string",
"null"
]
},
"mimeType": {
"type": [
"string",
"null"
]
},
"name": {
"type": "string"
},
"title": {
"type": [
"string",
"null"
]
},
"uriTemplate": {
"type": "string"
}
},
"required": [
"name",
"uriTemplate"
],
"type": "object"
},
"ResponseItem": {
"oneOf": [
{
"properties": {
"content": {
"items": {
"$ref": "#/definitions/ContentItem"
},
"type": "array"
},
"end_turn": {
"type": [
"boolean",
"null"
]
},
"id": {
"type": [
"string",
"null"
],
"writeOnly": true
},
"phase": {
"anyOf": [
{
"$ref": "#/definitions/MessagePhase"
},
{
"type": "null"
}
]
},
"role": {
"type": "string"
},
"type": {
"enum": [
"message"
],
"title": "MessageResponseItemType",
"type": "string"
}
},
"required": [
"content",
"role",
"type"
],
"title": "MessageResponseItem",
"type": "object"
},
{
"properties": {
"content": {
"default": null,
"items": {
"$ref": "#/definitions/ReasoningItemContent"
},
"type": [
"array",
"null"
]
},
"encrypted_content": {
"type": [
"string",
"null"
]
},
"id": {
"type": "string",
"writeOnly": true
},
"summary": {
"items": {
"$ref": "#/definitions/ReasoningItemReasoningSummary"
},
"type": "array"
},
"type": {
"enum": [
"reasoning"
],
"title": "ReasoningResponseItemType",
"type": "string"
}
},
"required": [
"id",
"summary",
"type"
],
"title": "ReasoningResponseItem",
"type": "object"
},
{
"properties": {
"action": {
"$ref": "#/definitions/LocalShellAction"
},
"call_id": {
"description": "Set when using the Responses API.",
"type": [
"string",
"null"
]
},
"id": {
"description": "Legacy id field retained for compatibility with older payloads.",
"type": [
"string",
"null"
],
"writeOnly": true
},
"status": {
"$ref": "#/definitions/LocalShellStatus"
},
"type": {
"enum": [
"local_shell_call"
],
"title": "LocalShellCallResponseItemType",
"type": "string"
}
},
"required": [
"action",
"status",
"type"
],
"title": "LocalShellCallResponseItem",
"type": "object"
},
{
"properties": {
"arguments": {
"type": "string"
},
"call_id": {
"type": "string"
},
"id": {
"type": [
"string",
"null"
],
"writeOnly": true
},
"name": {
"type": "string"
},
"type": {
"enum": [
"function_call"
],
"title": "FunctionCallResponseItemType",
"type": "string"
}
},
"required": [
"arguments",
"call_id",
"name",
"type"
],
"title": "FunctionCallResponseItem",
"type": "object"
},
{
"properties": {
"call_id": {
"type": "string"
},
"output": {
"$ref": "#/definitions/FunctionCallOutputPayload"
},
"type": {
"enum": [
"function_call_output"
],
"title": "FunctionCallOutputResponseItemType",
"type": "string"
}
},
"required": [
"call_id",
"output",
"type"
],
"title": "FunctionCallOutputResponseItem",
"type": "object"
},
{
"properties": {
"call_id": {
"type": "string"
},
"id": {
"type": [
"string",
"null"
],
"writeOnly": true
},
"input": {
"type": "string"
},
"name": {
"type": "string"
},
"status": {
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"custom_tool_call"
],
"title": "CustomToolCallResponseItemType",
"type": "string"
}
},
"required": [
"call_id",
"input",
"name",
"type"
],
"title": "CustomToolCallResponseItem",
"type": "object"
},
{
"properties": {
"call_id": {
"type": "string"
},
"output": {
Support multimodal custom tool outputs (#12948) ## Summary This changes `custom_tool_call_output` to use the same output payload shape as `function_call_output`, so freeform tools can return either plain text or structured content items. The main goal is to let `js_repl` return image content from nested `view_image` calls in its own `custom_tool_call_output`, instead of relying on a separate injected message. ## What changed - Changed `custom_tool_call_output.output` from `string` to `FunctionCallOutputPayload` - Updated freeform tool plumbing to preserve structured output bodies - Updated `js_repl` to aggregate nested tool content items and attach them to the outer `js_repl` result - Removed the old `js_repl` special case that injected `view_image` results as a separate pending user image message - Updated normalization/history/truncation paths to handle multimodal `custom_tool_call_output` - Regenerated app-server protocol schema artifacts ## Behavior Direct `view_image` calls still return a `function_call_output` with image content. When `view_image` is called inside `js_repl`, the outer `js_repl` `custom_tool_call_output` now carries: - an `input_text` item if the JS produced text output - one or more `input_image` items from nested tool results So the nested image result now stays inside the `js_repl` tool output instead of being injected as a separate message. ## Compatibility This is intended to be backward-compatible for resumed conversations. Older histories that stored `custom_tool_call_output.output` as a plain string still deserialize correctly, and older histories that used the previous injected-image-message flow also continue to resume. Added regression coverage for resuming a pre-change rollout containing: - string-valued `custom_tool_call_output` - legacy injected image message history #### [git stack](https://github.com/magus/git-stack-cli) - 👉 `1` https://github.com/openai/codex/pull/12948
2026-02-26 18:17:46 -08:00
"$ref": "#/definitions/FunctionCallOutputPayload"
},
"type": {
"enum": [
"custom_tool_call_output"
],
"title": "CustomToolCallOutputResponseItemType",
"type": "string"
}
},
"required": [
"call_id",
"output",
"type"
],
"title": "CustomToolCallOutputResponseItem",
"type": "object"
},
{
"properties": {
"action": {
"anyOf": [
{
"$ref": "#/definitions/ResponsesApiWebSearchAction"
},
{
"type": "null"
}
]
},
"id": {
"type": [
"string",
"null"
],
"writeOnly": true
},
"status": {
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"web_search_call"
],
"title": "WebSearchCallResponseItemType",
"type": "string"
}
},
"required": [
"type"
],
"title": "WebSearchCallResponseItem",
"type": "object"
},
{
"properties": {
"id": {
"type": "string"
},
"result": {
"type": "string"
},
"revised_prompt": {
"type": [
"string",
"null"
]
},
"status": {
"type": "string"
},
"type": {
"enum": [
"image_generation_call"
],
"title": "ImageGenerationCallResponseItemType",
"type": "string"
}
},
"required": [
"id",
"result",
"status",
"type"
],
"title": "ImageGenerationCallResponseItem",
"type": "object"
},
{
"properties": {
"ghost_commit": {
"$ref": "#/definitions/GhostCommit"
},
"type": {
"enum": [
"ghost_snapshot"
],
"title": "GhostSnapshotResponseItemType",
"type": "string"
}
},
"required": [
"ghost_commit",
"type"
],
"title": "GhostSnapshotResponseItem",
"type": "object"
},
{
"properties": {
"encrypted_content": {
"type": "string"
},
"type": {
"enum": [
"compaction"
],
"title": "CompactionResponseItemType",
"type": "string"
}
},
"required": [
"encrypted_content",
"type"
],
"title": "CompactionResponseItem",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"other"
],
"title": "OtherResponseItemType",
"type": "string"
}
},
"required": [
"type"
],
"title": "OtherResponseItem",
"type": "object"
}
]
},
"ResponsesApiWebSearchAction": {
"oneOf": [
{
"properties": {
"queries": {
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
},
"query": {
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"search"
],
"title": "SearchResponsesApiWebSearchActionType",
"type": "string"
}
},
"required": [
"type"
],
"title": "SearchResponsesApiWebSearchAction",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"open_page"
],
"title": "OpenPageResponsesApiWebSearchActionType",
"type": "string"
},
"url": {
"type": [
"string",
"null"
]
}
},
"required": [
"type"
],
"title": "OpenPageResponsesApiWebSearchAction",
"type": "object"
},
{
"properties": {
"pattern": {
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"find_in_page"
],
"title": "FindInPageResponsesApiWebSearchActionType",
"type": "string"
},
"url": {
"type": [
"string",
"null"
]
}
},
"required": [
"type"
],
"title": "FindInPageResponsesApiWebSearchAction",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"other"
],
"title": "OtherResponsesApiWebSearchActionType",
"type": "string"
}
},
"required": [
"type"
],
"title": "OtherResponsesApiWebSearchAction",
"type": "object"
}
]
},
"Result_of_CallToolResult_or_String": {
"oneOf": [
{
"properties": {
"Ok": {
"$ref": "#/definitions/CallToolResult"
}
},
"required": [
"Ok"
],
"title": "OkResult_of_CallToolResult_or_String",
"type": "object"
},
{
"properties": {
"Err": {
"type": "string"
}
},
"required": [
"Err"
],
"title": "ErrResult_of_CallToolResult_or_String",
"type": "object"
}
]
},
"ReviewCodeLocation": {
"description": "Location of the code related to a review finding.",
"properties": {
"absolute_file_path": {
"type": "string"
},
"line_range": {
"$ref": "#/definitions/ReviewLineRange"
}
},
"required": [
"absolute_file_path",
"line_range"
],
"type": "object"
},
feat: include available decisions in command approval requests (#12758) Command-approval clients currently infer which choices to show from side-channel fields like `networkApprovalContext`, `proposedExecpolicyAmendment`, and `additionalPermissions`. That makes the request shape harder to evolve, and it forces each client to replicate the server's heuristics instead of receiving the exact decision list for the prompt. This PR introduces a mapping between `CommandExecutionApprovalDecision` and `codex_protocol::protocol::ReviewDecision`: ```rust impl From<CoreReviewDecision> for CommandExecutionApprovalDecision { fn from(value: CoreReviewDecision) -> Self { match value { CoreReviewDecision::Approved => Self::Accept, CoreReviewDecision::ApprovedExecpolicyAmendment { proposed_execpolicy_amendment, } => Self::AcceptWithExecpolicyAmendment { execpolicy_amendment: proposed_execpolicy_amendment.into(), }, CoreReviewDecision::ApprovedForSession => Self::AcceptForSession, CoreReviewDecision::NetworkPolicyAmendment { network_policy_amendment, } => Self::ApplyNetworkPolicyAmendment { network_policy_amendment: network_policy_amendment.into(), }, CoreReviewDecision::Abort => Self::Cancel, CoreReviewDecision::Denied => Self::Decline, } } } ``` And updates `CommandExecutionRequestApprovalParams` to have a new field: ```rust available_decisions: Option<Vec<CommandExecutionApprovalDecision>> ``` when, if specified, should make it easier for clients to display an appropriate list of options in the UI. This makes it possible for `CoreShellActionProvider::prompt()` in `unix_escalation.rs` to specify the `Vec<ReviewDecision>` directly, adding support for `ApprovedForSession` when approving a skill script, which was previously missing in the TUI. Note this results in a significant change to `exec_options()` in `approval_overlay.rs`, as the displayed options are now derived from `available_decisions: &[ReviewDecision]`. ## What Changed - Add `available_decisions` to [`ExecApprovalRequestEvent`](https://github.com/openai/codex/blob/de00e932dd9801de0a4faac0519162099753f331/codex-rs/protocol/src/approvals.rs#L111-L175), including helpers to derive the legacy default choices when older senders omit the field. - Map `codex_protocol::protocol::ReviewDecision` to app-server `CommandExecutionApprovalDecision` and expose the ordered list as experimental `availableDecisions` in [`CommandExecutionRequestApprovalParams`](https://github.com/openai/codex/blob/de00e932dd9801de0a4faac0519162099753f331/codex-rs/app-server-protocol/src/protocol/v2.rs#L3798-L3807). - Thread optional `available_decisions` through the core approval path so Unix shell escalation can explicitly request `ApprovedForSession` for session-scoped approvals instead of relying on client heuristics. [`unix_escalation.rs`](https://github.com/openai/codex/blob/de00e932dd9801de0a4faac0519162099753f331/codex-rs/core/src/tools/runtimes/shell/unix_escalation.rs#L194-L214) - Update the TUI approval overlay to build its buttons from the ordered decision list, while preserving the legacy fallback when `available_decisions` is missing. - Update the app-server README, test client output, and generated schema artifacts to document and surface the new field. ## Testing - Add `approval_overlay.rs` coverage for explicit decision lists, including the generic `ApprovedForSession` path and network approval options. - Update `chatwidget/tests.rs` and app-server protocol tests to populate the new optional field and keep older event shapes working. ## Developers Docs - If we document `item/commandExecution/requestApproval` on [developers.openai.com/codex](https://developers.openai.com/codex), add experimental `availableDecisions` as the preferred source of approval choices and note that older servers may omit it.
2026-02-25 17:10:46 -08:00
"ReviewDecision": {
"description": "User's decision in response to an ExecApprovalRequest.",
"oneOf": [
{
"description": "User has approved this command and the agent should execute it.",
"enum": [
"approved"
],
"type": "string"
},
{
"additionalProperties": false,
"description": "User has approved this command and wants to apply the proposed execpolicy amendment so future matching commands are permitted.",
"properties": {
"approved_execpolicy_amendment": {
"properties": {
"proposed_execpolicy_amendment": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"proposed_execpolicy_amendment"
],
"type": "object"
}
},
"required": [
"approved_execpolicy_amendment"
],
"title": "ApprovedExecpolicyAmendmentReviewDecision",
"type": "object"
},
{
"description": "User has approved this request and wants future prompts in the same session-scoped approval cache to be automatically approved for the remainder of the session.",
"enum": [
"approved_for_session"
],
"type": "string"
},
{
"additionalProperties": false,
"description": "User chose to persist a network policy rule (allow/deny) for future requests to the same host.",
"properties": {
"network_policy_amendment": {
"properties": {
"network_policy_amendment": {
"$ref": "#/definitions/NetworkPolicyAmendment"
}
},
"required": [
"network_policy_amendment"
],
"type": "object"
}
},
"required": [
"network_policy_amendment"
],
"title": "NetworkPolicyAmendmentReviewDecision",
"type": "object"
},
{
"description": "User has denied this command and the agent should not execute it, but it should continue the session and try something else.",
"enum": [
"denied"
],
"type": "string"
},
{
"description": "User has denied this command and the agent should not do anything until the user's next command.",
"enum": [
"abort"
],
"type": "string"
}
]
},
"ReviewFinding": {
"description": "A single review finding describing an observed issue or recommendation.",
"properties": {
"body": {
"type": "string"
},
"code_location": {
"$ref": "#/definitions/ReviewCodeLocation"
},
"confidence_score": {
"format": "float",
"type": "number"
},
"priority": {
"format": "int32",
"type": "integer"
},
"title": {
"type": "string"
}
},
"required": [
"body",
"code_location",
"confidence_score",
"priority",
"title"
],
"type": "object"
},
"ReviewLineRange": {
"description": "Inclusive line range in a file associated with the finding.",
"properties": {
"end": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"start": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
}
},
"required": [
"end",
"start"
],
"type": "object"
},
"ReviewOutputEvent": {
"description": "Structured review result produced by a child review session.",
"properties": {
"findings": {
"items": {
"$ref": "#/definitions/ReviewFinding"
},
"type": "array"
},
"overall_confidence_score": {
"format": "float",
"type": "number"
},
"overall_correctness": {
"type": "string"
},
"overall_explanation": {
"type": "string"
}
},
"required": [
"findings",
"overall_confidence_score",
"overall_correctness",
"overall_explanation"
],
"type": "object"
},
"ReviewTarget": {
"oneOf": [
{
"description": "Review the working tree: staged, unstaged, and untracked files.",
"properties": {
"type": {
"enum": [
"uncommittedChanges"
],
"title": "UncommittedChangesReviewTargetType",
"type": "string"
}
},
"required": [
"type"
],
"title": "UncommittedChangesReviewTarget",
"type": "object"
},
{
"description": "Review changes between the current branch and the given base branch.",
"properties": {
"branch": {
"type": "string"
},
"type": {
"enum": [
"baseBranch"
],
"title": "BaseBranchReviewTargetType",
"type": "string"
}
},
"required": [
"branch",
"type"
],
"title": "BaseBranchReviewTarget",
"type": "object"
},
{
"description": "Review the changes introduced by a specific commit.",
"properties": {
"sha": {
"type": "string"
},
"title": {
"description": "Optional human-readable label (e.g., commit subject) for UIs.",
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"commit"
],
"title": "CommitReviewTargetType",
"type": "string"
}
},
"required": [
"sha",
"type"
],
"title": "CommitReviewTarget",
"type": "object"
},
{
"description": "Arbitrary instructions provided by the user.",
"properties": {
"instructions": {
"type": "string"
},
"type": {
"enum": [
"custom"
],
"title": "CustomReviewTargetType",
"type": "string"
}
},
"required": [
"instructions",
"type"
],
"title": "CustomReviewTarget",
"type": "object"
}
]
},
"SandboxPolicy": {
"description": "Determines execution restrictions for model shell commands.",
"oneOf": [
{
"description": "No restrictions whatsoever. Use with caution.",
"properties": {
"type": {
"enum": [
"danger-full-access"
],
"title": "DangerFullAccessSandboxPolicyType",
"type": "string"
}
},
"required": [
"type"
],
"title": "DangerFullAccessSandboxPolicy",
"type": "object"
},
{
feat: make sandbox read access configurable with `ReadOnlyAccess` (#11387) `SandboxPolicy::ReadOnly` previously implied broad read access and could not express a narrower read surface. This change introduces an explicit read-access model so we can support user-configurable read restrictions in follow-up work, while preserving current behavior today. It also ensures unsupported backends fail closed for restricted-read policies instead of silently granting broader access than intended. ## What - Added `ReadOnlyAccess` in protocol with: - `Restricted { include_platform_defaults, readable_roots }` - `FullAccess` - Updated `SandboxPolicy` to carry read-access configuration: - `ReadOnly { access: ReadOnlyAccess }` - `WorkspaceWrite { ..., read_only_access: ReadOnlyAccess }` - Preserved existing behavior by defaulting current construction paths to `ReadOnlyAccess::FullAccess`. - Threaded the new fields through sandbox policy consumers and call sites across `core`, `tui`, `linux-sandbox`, `windows-sandbox`, and related tests. - Updated Seatbelt policy generation to honor restricted read roots by emitting scoped read rules when full read access is not granted. - Added fail-closed behavior on Linux and Windows backends when restricted read access is requested but not yet implemented there (`UnsupportedOperation`). - Regenerated app-server protocol schema and TypeScript artifacts, including `ReadOnlyAccess`. ## Compatibility / rollout - Runtime behavior remains unchanged by default (`FullAccess`). - API/schema changes are in place so future config wiring can enable restricted read access without another policy-shape migration.
2026-02-11 18:31:14 -08:00
"description": "Read-only access configuration.",
"properties": {
feat: make sandbox read access configurable with `ReadOnlyAccess` (#11387) `SandboxPolicy::ReadOnly` previously implied broad read access and could not express a narrower read surface. This change introduces an explicit read-access model so we can support user-configurable read restrictions in follow-up work, while preserving current behavior today. It also ensures unsupported backends fail closed for restricted-read policies instead of silently granting broader access than intended. ## What - Added `ReadOnlyAccess` in protocol with: - `Restricted { include_platform_defaults, readable_roots }` - `FullAccess` - Updated `SandboxPolicy` to carry read-access configuration: - `ReadOnly { access: ReadOnlyAccess }` - `WorkspaceWrite { ..., read_only_access: ReadOnlyAccess }` - Preserved existing behavior by defaulting current construction paths to `ReadOnlyAccess::FullAccess`. - Threaded the new fields through sandbox policy consumers and call sites across `core`, `tui`, `linux-sandbox`, `windows-sandbox`, and related tests. - Updated Seatbelt policy generation to honor restricted read roots by emitting scoped read rules when full read access is not granted. - Added fail-closed behavior on Linux and Windows backends when restricted read access is requested but not yet implemented there (`UnsupportedOperation`). - Regenerated app-server protocol schema and TypeScript artifacts, including `ReadOnlyAccess`. ## Compatibility / rollout - Runtime behavior remains unchanged by default (`FullAccess`). - API/schema changes are in place so future config wiring can enable restricted read access without another policy-shape migration.
2026-02-11 18:31:14 -08:00
"access": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"description": "Read access granted while running under this policy."
},
"network_access": {
"description": "When set to `true`, outbound network access is allowed. `false` by default.",
"type": "boolean"
},
"type": {
"enum": [
"read-only"
],
"title": "ReadOnlySandboxPolicyType",
"type": "string"
}
},
"required": [
"type"
],
"title": "ReadOnlySandboxPolicy",
"type": "object"
},
{
"description": "Indicates the process is already in an external sandbox. Allows full disk access while honoring the provided network setting.",
"properties": {
"network_access": {
"allOf": [
{
"$ref": "#/definitions/NetworkAccess"
}
],
"default": "restricted",
"description": "Whether the external sandbox permits outbound network traffic."
},
"type": {
"enum": [
"external-sandbox"
],
"title": "ExternalSandboxSandboxPolicyType",
"type": "string"
}
},
"required": [
"type"
],
"title": "ExternalSandboxSandboxPolicy",
"type": "object"
},
{
"description": "Same as `ReadOnly` but additionally grants write access to the current working directory (\"workspace\").",
"properties": {
"exclude_slash_tmp": {
"default": false,
"description": "When set to `true`, will NOT include the `/tmp` among the default writable roots on UNIX. Defaults to `false`.",
"type": "boolean"
},
"exclude_tmpdir_env_var": {
"default": false,
"description": "When set to `true`, will NOT include the per-user `TMPDIR` environment variable among the default writable roots. Defaults to `false`.",
"type": "boolean"
},
"network_access": {
"default": false,
"description": "When set to `true`, outbound network access is allowed. `false` by default.",
"type": "boolean"
},
feat: make sandbox read access configurable with `ReadOnlyAccess` (#11387) `SandboxPolicy::ReadOnly` previously implied broad read access and could not express a narrower read surface. This change introduces an explicit read-access model so we can support user-configurable read restrictions in follow-up work, while preserving current behavior today. It also ensures unsupported backends fail closed for restricted-read policies instead of silently granting broader access than intended. ## What - Added `ReadOnlyAccess` in protocol with: - `Restricted { include_platform_defaults, readable_roots }` - `FullAccess` - Updated `SandboxPolicy` to carry read-access configuration: - `ReadOnly { access: ReadOnlyAccess }` - `WorkspaceWrite { ..., read_only_access: ReadOnlyAccess }` - Preserved existing behavior by defaulting current construction paths to `ReadOnlyAccess::FullAccess`. - Threaded the new fields through sandbox policy consumers and call sites across `core`, `tui`, `linux-sandbox`, `windows-sandbox`, and related tests. - Updated Seatbelt policy generation to honor restricted read roots by emitting scoped read rules when full read access is not granted. - Added fail-closed behavior on Linux and Windows backends when restricted read access is requested but not yet implemented there (`UnsupportedOperation`). - Regenerated app-server protocol schema and TypeScript artifacts, including `ReadOnlyAccess`. ## Compatibility / rollout - Runtime behavior remains unchanged by default (`FullAccess`). - API/schema changes are in place so future config wiring can enable restricted read access without another policy-shape migration.
2026-02-11 18:31:14 -08:00
"read_only_access": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"description": "Read access granted while running under this policy."
},
"type": {
"enum": [
"workspace-write"
],
"title": "WorkspaceWriteSandboxPolicyType",
"type": "string"
},
"writable_roots": {
"description": "Additional folders (beyond cwd and possibly TMPDIR) that should be writable from within the sandbox.",
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": "array"
}
},
"required": [
"type"
],
"title": "WorkspaceWriteSandboxPolicy",
"type": "object"
}
]
},
"ServiceTier": {
"enum": [
"fast",
"flex"
],
"type": "string"
},
"SessionNetworkProxyRuntime": {
"properties": {
"http_addr": {
"type": "string"
},
"socks_addr": {
"type": "string"
}
},
"required": [
"http_addr",
"socks_addr"
],
"type": "object"
},
"SkillDependencies": {
"properties": {
"tools": {
"items": {
"$ref": "#/definitions/SkillToolDependency"
},
"type": "array"
}
},
"required": [
"tools"
],
"type": "object"
},
"SkillErrorInfo": {
"properties": {
"message": {
"type": "string"
},
"path": {
"type": "string"
}
},
"required": [
"message",
"path"
],
"type": "object"
},
"SkillInterface": {
"properties": {
"brand_color": {
"type": [
"string",
"null"
]
},
"default_prompt": {
"type": [
"string",
"null"
]
},
"display_name": {
"type": [
"string",
"null"
]
},
"icon_large": {
"type": [
"string",
"null"
]
},
"icon_small": {
"type": [
"string",
"null"
]
},
"short_description": {
"type": [
"string",
"null"
]
}
},
"type": "object"
},
"SkillMetadata": {
"properties": {
"dependencies": {
"anyOf": [
{
"$ref": "#/definitions/SkillDependencies"
},
{
"type": "null"
}
]
},
"description": {
"type": "string"
},
"enabled": {
"type": "boolean"
},
"interface": {
"anyOf": [
{
"$ref": "#/definitions/SkillInterface"
},
{
"type": "null"
}
]
},
"name": {
"type": "string"
},
"path": {
"type": "string"
},
"scope": {
"$ref": "#/definitions/SkillScope"
},
"short_description": {
"description": "Legacy short_description from SKILL.md. Prefer SKILL.json interface.short_description.",
"type": [
"string",
"null"
]
}
},
"required": [
"description",
"enabled",
"name",
"path",
"scope"
],
"type": "object"
},
"SkillScope": {
"enum": [
"user",
"repo",
"system",
"admin"
],
"type": "string"
},
"SkillToolDependency": {
"properties": {
"command": {
"type": [
"string",
"null"
]
},
"description": {
"type": [
"string",
"null"
]
},
"transport": {
"type": [
"string",
"null"
]
},
"type": {
"type": "string"
},
"url": {
"type": [
"string",
"null"
]
},
"value": {
"type": "string"
}
},
"required": [
"type",
"value"
],
"type": "object"
},
"SkillsListEntry": {
"properties": {
"cwd": {
"type": "string"
},
"errors": {
"items": {
"$ref": "#/definitions/SkillErrorInfo"
},
"type": "array"
},
"skills": {
"items": {
"$ref": "#/definitions/SkillMetadata"
},
"type": "array"
}
},
"required": [
"cwd",
"errors",
"skills"
],
"type": "object"
},
"StepStatus": {
"enum": [
"pending",
"in_progress",
"completed"
],
"type": "string"
},
"TextElement": {
"properties": {
"byte_range": {
"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": [
"byte_range"
],
"type": "object"
},
"ThreadId": {
"type": "string"
},
"TokenUsage": {
"properties": {
"cached_input_tokens": {
"format": "int64",
"type": "integer"
},
"input_tokens": {
"format": "int64",
"type": "integer"
},
"output_tokens": {
"format": "int64",
"type": "integer"
},
"reasoning_output_tokens": {
"format": "int64",
"type": "integer"
},
"total_tokens": {
"format": "int64",
"type": "integer"
}
},
"required": [
"cached_input_tokens",
"input_tokens",
"output_tokens",
"reasoning_output_tokens",
"total_tokens"
],
"type": "object"
},
"TokenUsageInfo": {
"properties": {
"last_token_usage": {
"$ref": "#/definitions/TokenUsage"
},
"model_context_window": {
"format": "int64",
"type": [
"integer",
"null"
]
},
"total_token_usage": {
"$ref": "#/definitions/TokenUsage"
}
},
"required": [
"last_token_usage",
"total_token_usage"
],
"type": "object"
},
"Tool": {
"description": "Definition for a tool the client can call.",
"properties": {
feat: replace custom mcp-types crate with equivalents from rmcp (#10349) We started working with MCP in Codex before https://crates.io/crates/rmcp was mature, so we had our own crate for MCP types that was generated from the MCP schema: https://github.com/openai/codex/blob/8b95d3e082376f4cb23e92641705a22afb28a9da/codex-rs/mcp-types/README.md Now that `rmcp` is more mature, it makes more sense to use their MCP types in Rust, as they handle details (like the `_meta` field) that our custom version ignored. Though one advantage that our custom types had is that our generated types implemented `JsonSchema` and `ts_rs::TS`, whereas the types in `rmcp` do not. As such, part of the work of this PR is leveraging the adapters between `rmcp` types and the serializable types that are API for us (app server and MCP) introduced in #10356. Note this PR results in a number of changes to `codex-rs/app-server-protocol/schema`, which merit special attention during review. We must ensure that these changes are still backwards-compatible, which is possible because we have: ```diff - export type CallToolResult = { content: Array<ContentBlock>, isError?: boolean, structuredContent?: JsonValue, }; + export type CallToolResult = { content: Array<JsonValue>, structuredContent?: JsonValue, isError?: boolean, _meta?: JsonValue, }; ``` so `ContentBlock` has been replaced with the more general `JsonValue`. Note that `ContentBlock` was defined as: ```typescript export type ContentBlock = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource; ``` so the deletion of those individual variants should not be a cause of great concern. Similarly, we have the following change in `codex-rs/app-server-protocol/schema/typescript/Tool.ts`: ``` - export type Tool = { annotations?: ToolAnnotations, description?: string, inputSchema: ToolInputSchema, name: string, outputSchema?: ToolOutputSchema, title?: string, }; + export type Tool = { name: string, title?: string, description?: string, inputSchema: JsonValue, outputSchema?: JsonValue, annotations?: JsonValue, icons?: Array<JsonValue>, _meta?: JsonValue, }; ``` so: - `annotations?: ToolAnnotations` ➡️ `JsonValue` - `inputSchema: ToolInputSchema` ➡️ `JsonValue` - `outputSchema?: ToolOutputSchema` ➡️ `JsonValue` and two new fields: `icons?: Array<JsonValue>, _meta?: JsonValue` --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/10349). * #10357 * __->__ #10349 * #10356
2026-02-02 17:41:55 -08:00
"_meta": true,
"annotations": true,
"description": {
"type": [
"string",
"null"
]
},
feat: replace custom mcp-types crate with equivalents from rmcp (#10349) We started working with MCP in Codex before https://crates.io/crates/rmcp was mature, so we had our own crate for MCP types that was generated from the MCP schema: https://github.com/openai/codex/blob/8b95d3e082376f4cb23e92641705a22afb28a9da/codex-rs/mcp-types/README.md Now that `rmcp` is more mature, it makes more sense to use their MCP types in Rust, as they handle details (like the `_meta` field) that our custom version ignored. Though one advantage that our custom types had is that our generated types implemented `JsonSchema` and `ts_rs::TS`, whereas the types in `rmcp` do not. As such, part of the work of this PR is leveraging the adapters between `rmcp` types and the serializable types that are API for us (app server and MCP) introduced in #10356. Note this PR results in a number of changes to `codex-rs/app-server-protocol/schema`, which merit special attention during review. We must ensure that these changes are still backwards-compatible, which is possible because we have: ```diff - export type CallToolResult = { content: Array<ContentBlock>, isError?: boolean, structuredContent?: JsonValue, }; + export type CallToolResult = { content: Array<JsonValue>, structuredContent?: JsonValue, isError?: boolean, _meta?: JsonValue, }; ``` so `ContentBlock` has been replaced with the more general `JsonValue`. Note that `ContentBlock` was defined as: ```typescript export type ContentBlock = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource; ``` so the deletion of those individual variants should not be a cause of great concern. Similarly, we have the following change in `codex-rs/app-server-protocol/schema/typescript/Tool.ts`: ``` - export type Tool = { annotations?: ToolAnnotations, description?: string, inputSchema: ToolInputSchema, name: string, outputSchema?: ToolOutputSchema, title?: string, }; + export type Tool = { name: string, title?: string, description?: string, inputSchema: JsonValue, outputSchema?: JsonValue, annotations?: JsonValue, icons?: Array<JsonValue>, _meta?: JsonValue, }; ``` so: - `annotations?: ToolAnnotations` ➡️ `JsonValue` - `inputSchema: ToolInputSchema` ➡️ `JsonValue` - `outputSchema?: ToolOutputSchema` ➡️ `JsonValue` and two new fields: `icons?: Array<JsonValue>, _meta?: JsonValue` --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/10349). * #10357 * __->__ #10349 * #10356
2026-02-02 17:41:55 -08:00
"icons": {
"items": true,
"type": [
"array",
"null"
]
},
feat: replace custom mcp-types crate with equivalents from rmcp (#10349) We started working with MCP in Codex before https://crates.io/crates/rmcp was mature, so we had our own crate for MCP types that was generated from the MCP schema: https://github.com/openai/codex/blob/8b95d3e082376f4cb23e92641705a22afb28a9da/codex-rs/mcp-types/README.md Now that `rmcp` is more mature, it makes more sense to use their MCP types in Rust, as they handle details (like the `_meta` field) that our custom version ignored. Though one advantage that our custom types had is that our generated types implemented `JsonSchema` and `ts_rs::TS`, whereas the types in `rmcp` do not. As such, part of the work of this PR is leveraging the adapters between `rmcp` types and the serializable types that are API for us (app server and MCP) introduced in #10356. Note this PR results in a number of changes to `codex-rs/app-server-protocol/schema`, which merit special attention during review. We must ensure that these changes are still backwards-compatible, which is possible because we have: ```diff - export type CallToolResult = { content: Array<ContentBlock>, isError?: boolean, structuredContent?: JsonValue, }; + export type CallToolResult = { content: Array<JsonValue>, structuredContent?: JsonValue, isError?: boolean, _meta?: JsonValue, }; ``` so `ContentBlock` has been replaced with the more general `JsonValue`. Note that `ContentBlock` was defined as: ```typescript export type ContentBlock = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource; ``` so the deletion of those individual variants should not be a cause of great concern. Similarly, we have the following change in `codex-rs/app-server-protocol/schema/typescript/Tool.ts`: ``` - export type Tool = { annotations?: ToolAnnotations, description?: string, inputSchema: ToolInputSchema, name: string, outputSchema?: ToolOutputSchema, title?: string, }; + export type Tool = { name: string, title?: string, description?: string, inputSchema: JsonValue, outputSchema?: JsonValue, annotations?: JsonValue, icons?: Array<JsonValue>, _meta?: JsonValue, }; ``` so: - `annotations?: ToolAnnotations` ➡️ `JsonValue` - `inputSchema: ToolInputSchema` ➡️ `JsonValue` - `outputSchema?: ToolOutputSchema` ➡️ `JsonValue` and two new fields: `icons?: Array<JsonValue>, _meta?: JsonValue` --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/10349). * #10357 * __->__ #10349 * #10356
2026-02-02 17:41:55 -08:00
"inputSchema": true,
"name": {
"type": "string"
},
feat: replace custom mcp-types crate with equivalents from rmcp (#10349) We started working with MCP in Codex before https://crates.io/crates/rmcp was mature, so we had our own crate for MCP types that was generated from the MCP schema: https://github.com/openai/codex/blob/8b95d3e082376f4cb23e92641705a22afb28a9da/codex-rs/mcp-types/README.md Now that `rmcp` is more mature, it makes more sense to use their MCP types in Rust, as they handle details (like the `_meta` field) that our custom version ignored. Though one advantage that our custom types had is that our generated types implemented `JsonSchema` and `ts_rs::TS`, whereas the types in `rmcp` do not. As such, part of the work of this PR is leveraging the adapters between `rmcp` types and the serializable types that are API for us (app server and MCP) introduced in #10356. Note this PR results in a number of changes to `codex-rs/app-server-protocol/schema`, which merit special attention during review. We must ensure that these changes are still backwards-compatible, which is possible because we have: ```diff - export type CallToolResult = { content: Array<ContentBlock>, isError?: boolean, structuredContent?: JsonValue, }; + export type CallToolResult = { content: Array<JsonValue>, structuredContent?: JsonValue, isError?: boolean, _meta?: JsonValue, }; ``` so `ContentBlock` has been replaced with the more general `JsonValue`. Note that `ContentBlock` was defined as: ```typescript export type ContentBlock = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource; ``` so the deletion of those individual variants should not be a cause of great concern. Similarly, we have the following change in `codex-rs/app-server-protocol/schema/typescript/Tool.ts`: ``` - export type Tool = { annotations?: ToolAnnotations, description?: string, inputSchema: ToolInputSchema, name: string, outputSchema?: ToolOutputSchema, title?: string, }; + export type Tool = { name: string, title?: string, description?: string, inputSchema: JsonValue, outputSchema?: JsonValue, annotations?: JsonValue, icons?: Array<JsonValue>, _meta?: JsonValue, }; ``` so: - `annotations?: ToolAnnotations` ➡️ `JsonValue` - `inputSchema: ToolInputSchema` ➡️ `JsonValue` - `outputSchema?: ToolOutputSchema` ➡️ `JsonValue` and two new fields: `icons?: Array<JsonValue>, _meta?: JsonValue` --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/10349). * #10357 * __->__ #10349 * #10356
2026-02-02 17:41:55 -08:00
"outputSchema": true,
"title": {
"type": [
"string",
"null"
]
}
},
"required": [
"inputSchema",
"name"
],
"type": "object"
},
"TurnAbortReason": {
"enum": [
"interrupted",
"replaced",
"review_ended"
],
"type": "string"
},
"TurnItem": {
"oneOf": [
{
"properties": {
"content": {
"items": {
"$ref": "#/definitions/UserInput"
},
"type": "array"
},
"id": {
"type": "string"
},
"type": {
"enum": [
"UserMessage"
],
"title": "UserMessageTurnItemType",
"type": "string"
}
},
"required": [
"content",
"id",
"type"
],
"title": "UserMessageTurnItem",
"type": "object"
},
{
"description": "Assistant-authored message payload used in turn-item streams.\n\n`phase` is optional because not all providers/models emit it. Consumers should use it when present, but retain legacy completion semantics when it is `None`.",
"properties": {
"content": {
"items": {
"$ref": "#/definitions/AgentMessageContent"
},
"type": "array"
},
"id": {
"type": "string"
},
"phase": {
"anyOf": [
{
"$ref": "#/definitions/MessagePhase"
},
{
"type": "null"
}
],
"description": "Optional phase metadata carried through from `ResponseItem::Message`.\n\nThis is currently used by TUI rendering to distinguish mid-turn commentary from a final answer and avoid status-indicator jitter."
},
"type": {
"enum": [
"AgentMessage"
],
"title": "AgentMessageTurnItemType",
"type": "string"
}
},
"required": [
"content",
"id",
"type"
],
"title": "AgentMessageTurnItem",
"type": "object"
},
{
"properties": {
"id": {
"type": "string"
},
"text": {
"type": "string"
},
"type": {
"enum": [
"Plan"
],
"title": "PlanTurnItemType",
"type": "string"
}
},
"required": [
"id",
"text",
"type"
],
"title": "PlanTurnItem",
"type": "object"
},
{
"properties": {
"id": {
"type": "string"
},
"raw_content": {
"default": [],
"items": {
"type": "string"
},
"type": "array"
},
"summary_text": {
"items": {
"type": "string"
},
"type": "array"
},
"type": {
"enum": [
"Reasoning"
],
"title": "ReasoningTurnItemType",
"type": "string"
}
},
"required": [
"id",
"summary_text",
"type"
],
"title": "ReasoningTurnItem",
"type": "object"
},
{
"properties": {
"action": {
"$ref": "#/definitions/ResponsesApiWebSearchAction"
},
"id": {
"type": "string"
},
"query": {
"type": "string"
},
"type": {
"enum": [
"WebSearch"
],
"title": "WebSearchTurnItemType",
"type": "string"
}
},
"required": [
"action",
"id",
"query",
"type"
],
"title": "WebSearchTurnItem",
"type": "object"
},
{
"properties": {
"id": {
"type": "string"
},
"result": {
"type": "string"
},
"revised_prompt": {
"type": [
"string",
"null"
]
},
"saved_path": {
"type": [
"string",
"null"
]
},
"status": {
"type": "string"
},
"type": {
"enum": [
"ImageGeneration"
],
"title": "ImageGenerationTurnItemType",
"type": "string"
}
},
"required": [
"id",
"result",
"status",
"type"
],
"title": "ImageGenerationTurnItem",
"type": "object"
},
{
"properties": {
"id": {
"type": "string"
},
"type": {
"enum": [
"ContextCompaction"
],
"title": "ContextCompactionTurnItemType",
"type": "string"
}
},
"required": [
"id",
"type"
],
"title": "ContextCompactionTurnItem",
"type": "object"
}
]
},
"UserInput": {
"description": "User input",
"oneOf": [
{
"properties": {
"text": {
"type": "string"
},
"text_elements": {
"default": [],
"description": "UI-defined spans within `text` that should be treated as special elements. These are byte ranges into the UTF-8 `text` buffer and are used to render or persist rich input markers (e.g., image placeholders) across history and resume without mutating the literal text.",
"items": {
"$ref": "#/definitions/TextElement"
},
"type": "array"
},
"type": {
"enum": [
"text"
],
"title": "TextUserInputType",
"type": "string"
}
},
"required": [
"text",
"type"
],
"title": "TextUserInput",
"type": "object"
},
{
"description": "Preencoded data: URI image.",
"properties": {
"image_url": {
"type": "string"
},
"type": {
"enum": [
"image"
],
"title": "ImageUserInputType",
"type": "string"
}
},
"required": [
"image_url",
"type"
],
"title": "ImageUserInput",
"type": "object"
},
{
"description": "Local image path provided by the user. This will be converted to an `Image` variant (base64 data URL) during request serialization.",
"properties": {
"path": {
"type": "string"
},
"type": {
"enum": [
"local_image"
],
"title": "LocalImageUserInputType",
"type": "string"
}
},
"required": [
"path",
"type"
],
"title": "LocalImageUserInput",
"type": "object"
},
{
"description": "Skill selected by the user (name + path to SKILL.md).",
"properties": {
"name": {
"type": "string"
},
"path": {
"type": "string"
},
"type": {
"enum": [
"skill"
],
"title": "SkillUserInputType",
"type": "string"
}
},
"required": [
"name",
"path",
"type"
],
"title": "SkillUserInput",
"type": "object"
},
{
"description": "Explicit structured mention selected by the user.\n\n`path` identifies the exact mention target, for example `app://<connector-id>` or `plugin://<plugin-name>@<marketplace-name>`.",
"properties": {
"name": {
"type": "string"
},
"path": {
"type": "string"
},
"type": {
"enum": [
"mention"
],
"title": "MentionUserInputType",
"type": "string"
}
},
"required": [
"name",
"path",
"type"
],
"title": "MentionUserInput",
"type": "object"
}
]
}
},
"description": "Response event from the agent NOTE: Make sure none of these values have optional types, as it will mess up the extension code-gen.",
"oneOf": [
{
"description": "Error while executing a submission",
"properties": {
"codex_error_info": {
"anyOf": [
{
"$ref": "#/definitions/CodexErrorInfo"
},
{
"type": "null"
}
],
"default": null
},
"message": {
"type": "string"
},
"type": {
"enum": [
"error"
],
"title": "ErrorEventMsgType",
"type": "string"
}
},
"required": [
"message",
"type"
],
"title": "ErrorEventMsg",
"type": "object"
},
{
"description": "Warning issued while processing a submission. Unlike `Error`, this indicates the turn continued but the user should still be notified.",
"properties": {
"message": {
"type": "string"
},
"type": {
"enum": [
"warning"
],
"title": "WarningEventMsgType",
"type": "string"
}
},
"required": [
"message",
"type"
],
"title": "WarningEventMsg",
"type": "object"
},
{
"description": "Realtime conversation lifecycle start event.",
"properties": {
"session_id": {
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"realtime_conversation_started"
],
"title": "RealtimeConversationStartedEventMsgType",
"type": "string"
}
},
"required": [
"type"
],
"title": "RealtimeConversationStartedEventMsg",
"type": "object"
},
{
"description": "Realtime conversation streaming payload event.",
"properties": {
"payload": {
"$ref": "#/definitions/RealtimeEvent"
},
"type": {
"enum": [
"realtime_conversation_realtime"
],
"title": "RealtimeConversationRealtimeEventMsgType",
"type": "string"
}
},
"required": [
"payload",
"type"
],
"title": "RealtimeConversationRealtimeEventMsg",
"type": "object"
},
{
"description": "Realtime conversation lifecycle close event.",
"properties": {
"reason": {
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"realtime_conversation_closed"
],
"title": "RealtimeConversationClosedEventMsgType",
"type": "string"
}
},
"required": [
"type"
],
"title": "RealtimeConversationClosedEventMsg",
"type": "object"
},
{
"description": "Model routing changed from the requested model to a different model.",
"properties": {
"from_model": {
"type": "string"
},
"reason": {
"$ref": "#/definitions/ModelRerouteReason"
},
"to_model": {
"type": "string"
},
"type": {
"enum": [
"model_reroute"
],
"title": "ModelRerouteEventMsgType",
"type": "string"
}
},
"required": [
"from_model",
"reason",
"to_model",
"type"
],
"title": "ModelRerouteEventMsg",
"type": "object"
},
{
"description": "Conversation history was compacted (either automatically or manually).",
"properties": {
"type": {
"enum": [
"context_compacted"
],
"title": "ContextCompactedEventMsgType",
"type": "string"
}
},
"required": [
"type"
],
"title": "ContextCompactedEventMsg",
"type": "object"
},
{
"description": "Conversation history was rolled back by dropping the last N user turns.",
"properties": {
"num_turns": {
"description": "Number of user turns that were removed from context.",
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"type": {
"enum": [
"thread_rolled_back"
],
"title": "ThreadRolledBackEventMsgType",
"type": "string"
}
},
"required": [
"num_turns",
"type"
],
"title": "ThreadRolledBackEventMsg",
"type": "object"
},
{
"description": "Agent has started a turn. v1 wire format uses `task_started`; accept `turn_started` for v2 interop.",
"properties": {
"collaboration_mode_kind": {
"allOf": [
{
"$ref": "#/definitions/ModeKind"
}
],
Cleanup collaboration mode variants (#10404) ## Summary This PR simplifies collaboration modes to the visible set `default | plan`, while preserving backward compatibility for older partners that may still send legacy mode names. Specifically: - Renames the old Code behavior to **Default**. - Keeps **Plan** as-is. - Removes **Custom** mode behavior (fallbacks now resolve to Default). - Keeps `PairProgramming` and `Execute` internally for compatibility plumbing, while removing them from schema/API and UI visibility. - Adds legacy input aliasing so older clients can still send old mode names. ## What Changed 1. Mode enum and compatibility - `ModeKind` now uses `Plan` + `Default` as active/public modes. - `ModeKind::Default` deserialization accepts legacy values: - `code` - `pair_programming` - `execute` - `custom` - `PairProgramming` and `Execute` variants remain in code but are hidden from protocol/schema generation. - `Custom` variant is removed; previous custom fallbacks now map to `Default`. 2. Collaboration presets and templates - Built-in presets now return only: - `Plan` - `Default` - Template rename: - `core/templates/collaboration_mode/code.md` -> `default.md` - `execute.md` and `pair_programming.md` remain on disk but are not surfaced in visible preset lists. 3. TUI updates - Updated user-facing naming and prompts from “Code” to “Default”. - Updated mode-cycle and indicator behavior to reflect only visible `Plan` and `Default`. - Updated corresponding tests and snapshots. 4. request_user_input behavior - `request_user_input` remains allowed only in `Plan` mode. - Rejection messaging now consistently treats non-plan modes as `Default`. 5. Schemas - Regenerated config and app-server schemas. - Public schema types now advertise mode values as: - `plan` - `default` ## Backward Compatibility Notes - Incoming legacy mode names (`code`, `pair_programming`, `execute`, `custom`) are accepted and coerced to `default`. - Outgoing/public schema surfaces intentionally expose only `plan | default`. - This allows tolerant ingestion of older partner payloads while standardizing new integrations on the reduced mode set. ## Codex author `codex fork 019c1fae-693b-7840-b16e-9ad38ea0bd00`
2026-02-03 09:23:53 -08:00
"default": "default"
},
"model_context_window": {
"format": "int64",
"type": [
"integer",
"null"
]
},
chore: persist turn_id in rollout session and make turn_id uuid based (#11246) Problem: 1. turn id is constructed in-memory; 2. on resuming threads, turn_id might not be unique; 3. client cannot no the boundary of a turn from rollout files easily. This PR does three things: 1. persist `task_started` and `task_complete` events; 1. persist `turn_id` in rollout turn events; 5. generate turn_id as unique uuids instead of incrementing it in memory. This helps us resolve the issue of clients wanting to have unique turn ids for resuming a thread, and knowing the boundry of each turn in rollout files. example debug logs ``` 2026-02-11T00:32:10.746876Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=8 turn=Turn { id: "019c4a07-d809-74c3-bc4b-fd9618487b4b", items: [UserMessage { id: "item-24", content: [Text { text: "hi", text_elements: [] }] }, AgentMessage { id: "item-25", text: "Hi. I’m in the workspace with your current changes loaded and ready. Send the next task and I’ll execute it end-to-end." }], status: Completed, error: None } 2026-02-11T00:32:10.746888Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=9 turn=Turn { id: "019c4a18-1004-76c0-a0fb-a77610f6a9b8", items: [UserMessage { id: "item-26", content: [Text { text: "hello", text_elements: [] }] }, AgentMessage { id: "item-27", text: "Hello. Ready for the next change in `codex-rs`; I can continue from the current in-progress diff or start a new task." }], status: Completed, error: None } 2026-02-11T00:32:10.746899Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=10 turn=Turn { id: "019c4a19-41f0-7db0-ad78-74f1503baeb8", items: [UserMessage { id: "item-28", content: [Text { text: "hello", text_elements: [] }] }, AgentMessage { id: "item-29", text: "Hello. Send the specific change you want in `codex-rs`, and I’ll implement it and run the required checks." }], status: Completed, error: None } ``` backward compatibility: if you try to resume an old session without task_started and task_complete event populated, the following happens: - If you resume and do nothing: those reconstructed historical IDs can differ next time you resume. - If you resume and send a new turn: the new turn gets a fresh UUID from live submission flow and is persisted, so that new turn’s ID is stable on later resumes. I think this behavior is fine, because we only care about deterministic turn id once a turn is triggered.
2026-02-10 19:56:01 -08:00
"turn_id": {
"type": "string"
},
"type": {
"enum": [
"task_started"
],
"title": "TaskStartedEventMsgType",
"type": "string"
}
},
"required": [
chore: persist turn_id in rollout session and make turn_id uuid based (#11246) Problem: 1. turn id is constructed in-memory; 2. on resuming threads, turn_id might not be unique; 3. client cannot no the boundary of a turn from rollout files easily. This PR does three things: 1. persist `task_started` and `task_complete` events; 1. persist `turn_id` in rollout turn events; 5. generate turn_id as unique uuids instead of incrementing it in memory. This helps us resolve the issue of clients wanting to have unique turn ids for resuming a thread, and knowing the boundry of each turn in rollout files. example debug logs ``` 2026-02-11T00:32:10.746876Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=8 turn=Turn { id: "019c4a07-d809-74c3-bc4b-fd9618487b4b", items: [UserMessage { id: "item-24", content: [Text { text: "hi", text_elements: [] }] }, AgentMessage { id: "item-25", text: "Hi. I’m in the workspace with your current changes loaded and ready. Send the next task and I’ll execute it end-to-end." }], status: Completed, error: None } 2026-02-11T00:32:10.746888Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=9 turn=Turn { id: "019c4a18-1004-76c0-a0fb-a77610f6a9b8", items: [UserMessage { id: "item-26", content: [Text { text: "hello", text_elements: [] }] }, AgentMessage { id: "item-27", text: "Hello. Ready for the next change in `codex-rs`; I can continue from the current in-progress diff or start a new task." }], status: Completed, error: None } 2026-02-11T00:32:10.746899Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=10 turn=Turn { id: "019c4a19-41f0-7db0-ad78-74f1503baeb8", items: [UserMessage { id: "item-28", content: [Text { text: "hello", text_elements: [] }] }, AgentMessage { id: "item-29", text: "Hello. Send the specific change you want in `codex-rs`, and I’ll implement it and run the required checks." }], status: Completed, error: None } ``` backward compatibility: if you try to resume an old session without task_started and task_complete event populated, the following happens: - If you resume and do nothing: those reconstructed historical IDs can differ next time you resume. - If you resume and send a new turn: the new turn gets a fresh UUID from live submission flow and is persisted, so that new turn’s ID is stable on later resumes. I think this behavior is fine, because we only care about deterministic turn id once a turn is triggered.
2026-02-10 19:56:01 -08:00
"turn_id",
"type"
],
"title": "TaskStartedEventMsg",
"type": "object"
},
{
"description": "Agent has completed all actions. v1 wire format uses `task_complete`; accept `turn_complete` for v2 interop.",
"properties": {
"last_agent_message": {
"type": [
"string",
"null"
]
},
chore: persist turn_id in rollout session and make turn_id uuid based (#11246) Problem: 1. turn id is constructed in-memory; 2. on resuming threads, turn_id might not be unique; 3. client cannot no the boundary of a turn from rollout files easily. This PR does three things: 1. persist `task_started` and `task_complete` events; 1. persist `turn_id` in rollout turn events; 5. generate turn_id as unique uuids instead of incrementing it in memory. This helps us resolve the issue of clients wanting to have unique turn ids for resuming a thread, and knowing the boundry of each turn in rollout files. example debug logs ``` 2026-02-11T00:32:10.746876Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=8 turn=Turn { id: "019c4a07-d809-74c3-bc4b-fd9618487b4b", items: [UserMessage { id: "item-24", content: [Text { text: "hi", text_elements: [] }] }, AgentMessage { id: "item-25", text: "Hi. I’m in the workspace with your current changes loaded and ready. Send the next task and I’ll execute it end-to-end." }], status: Completed, error: None } 2026-02-11T00:32:10.746888Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=9 turn=Turn { id: "019c4a18-1004-76c0-a0fb-a77610f6a9b8", items: [UserMessage { id: "item-26", content: [Text { text: "hello", text_elements: [] }] }, AgentMessage { id: "item-27", text: "Hello. Ready for the next change in `codex-rs`; I can continue from the current in-progress diff or start a new task." }], status: Completed, error: None } 2026-02-11T00:32:10.746899Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=10 turn=Turn { id: "019c4a19-41f0-7db0-ad78-74f1503baeb8", items: [UserMessage { id: "item-28", content: [Text { text: "hello", text_elements: [] }] }, AgentMessage { id: "item-29", text: "Hello. Send the specific change you want in `codex-rs`, and I’ll implement it and run the required checks." }], status: Completed, error: None } ``` backward compatibility: if you try to resume an old session without task_started and task_complete event populated, the following happens: - If you resume and do nothing: those reconstructed historical IDs can differ next time you resume. - If you resume and send a new turn: the new turn gets a fresh UUID from live submission flow and is persisted, so that new turn’s ID is stable on later resumes. I think this behavior is fine, because we only care about deterministic turn id once a turn is triggered.
2026-02-10 19:56:01 -08:00
"turn_id": {
"type": "string"
},
"type": {
"enum": [
"task_complete"
],
"title": "TaskCompleteEventMsgType",
"type": "string"
}
},
"required": [
chore: persist turn_id in rollout session and make turn_id uuid based (#11246) Problem: 1. turn id is constructed in-memory; 2. on resuming threads, turn_id might not be unique; 3. client cannot no the boundary of a turn from rollout files easily. This PR does three things: 1. persist `task_started` and `task_complete` events; 1. persist `turn_id` in rollout turn events; 5. generate turn_id as unique uuids instead of incrementing it in memory. This helps us resolve the issue of clients wanting to have unique turn ids for resuming a thread, and knowing the boundry of each turn in rollout files. example debug logs ``` 2026-02-11T00:32:10.746876Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=8 turn=Turn { id: "019c4a07-d809-74c3-bc4b-fd9618487b4b", items: [UserMessage { id: "item-24", content: [Text { text: "hi", text_elements: [] }] }, AgentMessage { id: "item-25", text: "Hi. I’m in the workspace with your current changes loaded and ready. Send the next task and I’ll execute it end-to-end." }], status: Completed, error: None } 2026-02-11T00:32:10.746888Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=9 turn=Turn { id: "019c4a18-1004-76c0-a0fb-a77610f6a9b8", items: [UserMessage { id: "item-26", content: [Text { text: "hello", text_elements: [] }] }, AgentMessage { id: "item-27", text: "Hello. Ready for the next change in `codex-rs`; I can continue from the current in-progress diff or start a new task." }], status: Completed, error: None } 2026-02-11T00:32:10.746899Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=10 turn=Turn { id: "019c4a19-41f0-7db0-ad78-74f1503baeb8", items: [UserMessage { id: "item-28", content: [Text { text: "hello", text_elements: [] }] }, AgentMessage { id: "item-29", text: "Hello. Send the specific change you want in `codex-rs`, and I’ll implement it and run the required checks." }], status: Completed, error: None } ``` backward compatibility: if you try to resume an old session without task_started and task_complete event populated, the following happens: - If you resume and do nothing: those reconstructed historical IDs can differ next time you resume. - If you resume and send a new turn: the new turn gets a fresh UUID from live submission flow and is persisted, so that new turn’s ID is stable on later resumes. I think this behavior is fine, because we only care about deterministic turn id once a turn is triggered.
2026-02-10 19:56:01 -08:00
"turn_id",
"type"
],
"title": "TaskCompleteEventMsg",
"type": "object"
},
{
"description": "Usage update for the current session, including totals and last turn. Optional means unknown — UIs should not display when `None`.",
"properties": {
"info": {
"anyOf": [
{
"$ref": "#/definitions/TokenUsageInfo"
},
{
"type": "null"
}
]
},
"rate_limits": {
"anyOf": [
{
"$ref": "#/definitions/RateLimitSnapshot"
},
{
"type": "null"
}
]
},
"type": {
"enum": [
"token_count"
],
"title": "TokenCountEventMsgType",
"type": "string"
}
},
"required": [
"type"
],
"title": "TokenCountEventMsg",
"type": "object"
},
{
"description": "Agent text output message",
"properties": {
"message": {
"type": "string"
},
"phase": {
"anyOf": [
{
"$ref": "#/definitions/MessagePhase"
},
{
"type": "null"
}
],
"default": null
},
"type": {
"enum": [
"agent_message"
],
"title": "AgentMessageEventMsgType",
"type": "string"
}
},
"required": [
"message",
"type"
],
"title": "AgentMessageEventMsg",
"type": "object"
},
{
"description": "User/system input message (what was sent to the model)",
"properties": {
"images": {
"description": "Image URLs sourced from `UserInput::Image`. These are safe to replay in legacy UI history events and correspond to images sent to the model.",
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
},
"local_images": {
"default": [],
"description": "Local file paths sourced from `UserInput::LocalImage`. These are kept so the UI can reattach images when editing history, and should not be sent to the model or treated as API-ready URLs.",
"items": {
"type": "string"
},
"type": "array"
},
"message": {
"type": "string"
},
"text_elements": {
"default": [],
"description": "UI-defined spans within `message` used to render or persist special elements.",
"items": {
"$ref": "#/definitions/TextElement"
},
"type": "array"
},
"type": {
"enum": [
"user_message"
],
"title": "UserMessageEventMsgType",
"type": "string"
}
},
"required": [
"message",
"type"
],
"title": "UserMessageEventMsg",
"type": "object"
},
{
"description": "Agent text output delta message",
"properties": {
"delta": {
"type": "string"
},
"type": {
"enum": [
"agent_message_delta"
],
"title": "AgentMessageDeltaEventMsgType",
"type": "string"
}
},
"required": [
"delta",
"type"
],
"title": "AgentMessageDeltaEventMsg",
"type": "object"
},
{
"description": "Reasoning event from agent.",
"properties": {
"text": {
"type": "string"
},
"type": {
"enum": [
"agent_reasoning"
],
"title": "AgentReasoningEventMsgType",
"type": "string"
}
},
"required": [
"text",
"type"
],
"title": "AgentReasoningEventMsg",
"type": "object"
},
{
"description": "Agent reasoning delta event from agent.",
"properties": {
"delta": {
"type": "string"
},
"type": {
"enum": [
"agent_reasoning_delta"
],
"title": "AgentReasoningDeltaEventMsgType",
"type": "string"
}
},
"required": [
"delta",
"type"
],
"title": "AgentReasoningDeltaEventMsg",
"type": "object"
},
{
"description": "Raw chain-of-thought from agent.",
"properties": {
"text": {
"type": "string"
},
"type": {
"enum": [
"agent_reasoning_raw_content"
],
"title": "AgentReasoningRawContentEventMsgType",
"type": "string"
}
},
"required": [
"text",
"type"
],
"title": "AgentReasoningRawContentEventMsg",
"type": "object"
},
{
"description": "Agent reasoning content delta event from agent.",
"properties": {
"delta": {
"type": "string"
},
"type": {
"enum": [
"agent_reasoning_raw_content_delta"
],
"title": "AgentReasoningRawContentDeltaEventMsgType",
"type": "string"
}
},
"required": [
"delta",
"type"
],
"title": "AgentReasoningRawContentDeltaEventMsg",
"type": "object"
},
{
"description": "Signaled when the model begins a new reasoning summary section (e.g., a new titled block).",
"properties": {
"item_id": {
"default": "",
"type": "string"
},
"summary_index": {
"default": 0,
"format": "int64",
"type": "integer"
},
"type": {
"enum": [
"agent_reasoning_section_break"
],
"title": "AgentReasoningSectionBreakEventMsgType",
"type": "string"
}
},
"required": [
"type"
],
"title": "AgentReasoningSectionBreakEventMsg",
"type": "object"
},
{
"description": "Ack the client's configure message.",
"properties": {
"approval_policy": {
"allOf": [
{
"$ref": "#/definitions/AskForApproval"
}
],
"description": "When to escalate for approval for execution"
},
"cwd": {
"description": "Working directory that should be treated as the *root* of the session.",
"type": "string"
},
"forked_from_id": {
"anyOf": [
{
"$ref": "#/definitions/ThreadId"
},
{
"type": "null"
}
]
},
"history_entry_count": {
"description": "Current number of entries in the history log.",
"format": "uint",
"minimum": 0.0,
"type": "integer"
},
"history_log_id": {
"description": "Identifier of the history log file (inode on Unix, 0 otherwise).",
"format": "uint64",
"minimum": 0.0,
"type": "integer"
},
"initial_messages": {
"description": "Optional initial messages (as events) for resumed sessions. When present, UIs can use these to seed the history.",
"items": {
"$ref": "#/definitions/EventMsg"
},
"type": [
"array",
"null"
]
},
"model": {
"description": "Tell the client what model is being queried.",
"type": "string"
},
"model_provider_id": {
"type": "string"
},
"network_proxy": {
"anyOf": [
{
"$ref": "#/definitions/SessionNetworkProxyRuntime"
},
{
"type": "null"
}
],
"description": "Runtime proxy bind addresses, when the managed proxy was started for this session."
},
"reasoning_effort": {
"anyOf": [
{
"$ref": "#/definitions/ReasoningEffort"
},
{
"type": "null"
}
],
"description": "The effort the model is putting into reasoning about the user's request."
},
"rollout_path": {
"description": "Path in which the rollout is stored. Can be `None` for ephemeral threads",
"type": [
"string",
"null"
]
},
"sandbox_policy": {
"allOf": [
{
"$ref": "#/definitions/SandboxPolicy"
}
],
"description": "How to sandbox commands executed in the system"
},
"service_tier": {
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
]
},
"session_id": {
"$ref": "#/definitions/ThreadId"
},
"thread_name": {
"description": "Optional user-facing thread name (may be unset).",
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"session_configured"
],
"title": "SessionConfiguredEventMsgType",
"type": "string"
}
},
"required": [
"approval_policy",
"cwd",
"history_entry_count",
"history_log_id",
"model",
"model_provider_id",
"sandbox_policy",
"session_id",
"type"
],
"title": "SessionConfiguredEventMsg",
"type": "object"
},
{
"description": "Updated session metadata (e.g., thread name changes).",
"properties": {
"thread_id": {
"$ref": "#/definitions/ThreadId"
},
"thread_name": {
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"thread_name_updated"
],
"title": "ThreadNameUpdatedEventMsgType",
"type": "string"
}
},
"required": [
"thread_id",
"type"
],
"title": "ThreadNameUpdatedEventMsg",
"type": "object"
},
{
"description": "Incremental MCP startup progress updates.",
"properties": {
"server": {
"description": "Server name being started.",
"type": "string"
},
"status": {
"allOf": [
{
"$ref": "#/definitions/McpStartupStatus"
}
],
"description": "Current startup status."
},
"type": {
"enum": [
"mcp_startup_update"
],
"title": "McpStartupUpdateEventMsgType",
"type": "string"
}
},
"required": [
"server",
"status",
"type"
],
"title": "McpStartupUpdateEventMsg",
"type": "object"
},
{
"description": "Aggregate MCP startup completion summary.",
"properties": {
"cancelled": {
"items": {
"type": "string"
},
"type": "array"
},
"failed": {
"items": {
"$ref": "#/definitions/McpStartupFailure"
},
"type": "array"
},
"ready": {
"items": {
"type": "string"
},
"type": "array"
},
"type": {
"enum": [
"mcp_startup_complete"
],
"title": "McpStartupCompleteEventMsgType",
"type": "string"
}
},
"required": [
"cancelled",
"failed",
"ready",
"type"
],
"title": "McpStartupCompleteEventMsg",
"type": "object"
},
{
"properties": {
"call_id": {
"description": "Identifier so this can be paired with the McpToolCallEnd event.",
"type": "string"
},
"invocation": {
"$ref": "#/definitions/McpInvocation"
},
"type": {
"enum": [
"mcp_tool_call_begin"
],
"title": "McpToolCallBeginEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"invocation",
"type"
],
"title": "McpToolCallBeginEventMsg",
"type": "object"
},
{
"properties": {
"call_id": {
"description": "Identifier for the corresponding McpToolCallBegin that finished.",
"type": "string"
},
"duration": {
"$ref": "#/definitions/Duration"
},
"invocation": {
"$ref": "#/definitions/McpInvocation"
},
"result": {
"allOf": [
{
"$ref": "#/definitions/Result_of_CallToolResult_or_String"
}
],
"description": "Result of the tool call. Note this could be an error."
},
"type": {
"enum": [
"mcp_tool_call_end"
],
"title": "McpToolCallEndEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"duration",
"invocation",
"result",
"type"
],
"title": "McpToolCallEndEventMsg",
"type": "object"
},
{
"properties": {
"call_id": {
"type": "string"
},
"type": {
"enum": [
"web_search_begin"
],
"title": "WebSearchBeginEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"type"
],
"title": "WebSearchBeginEventMsg",
"type": "object"
},
{
"properties": {
"action": {
"$ref": "#/definitions/ResponsesApiWebSearchAction"
},
"call_id": {
"type": "string"
},
"query": {
"type": "string"
},
"type": {
"enum": [
"web_search_end"
],
"title": "WebSearchEndEventMsgType",
"type": "string"
}
},
"required": [
"action",
"call_id",
"query",
"type"
],
"title": "WebSearchEndEventMsg",
"type": "object"
},
{
"properties": {
"call_id": {
"type": "string"
},
"type": {
"enum": [
"image_generation_begin"
],
"title": "ImageGenerationBeginEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"type"
],
"title": "ImageGenerationBeginEventMsg",
"type": "object"
},
{
"properties": {
"call_id": {
"type": "string"
},
"result": {
"type": "string"
},
"revised_prompt": {
"type": [
"string",
"null"
]
},
"saved_path": {
"type": [
"string",
"null"
]
},
"status": {
"type": "string"
},
"type": {
"enum": [
"image_generation_end"
],
"title": "ImageGenerationEndEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"result",
"status",
"type"
],
"title": "ImageGenerationEndEventMsg",
"type": "object"
},
{
"description": "Notification that the server is about to execute a command.",
"properties": {
"call_id": {
"description": "Identifier so this can be paired with the ExecCommandEnd event.",
"type": "string"
},
"command": {
"description": "The command to be executed.",
"items": {
"type": "string"
},
"type": "array"
},
"cwd": {
"description": "The command's working directory if not the default cwd for the agent.",
"type": "string"
},
"interaction_input": {
"description": "Raw input sent to a unified exec session (if this is an interaction event).",
"type": [
"string",
"null"
]
},
"parsed_cmd": {
"items": {
"$ref": "#/definitions/ParsedCommand"
},
"type": "array"
},
"process_id": {
"description": "Identifier for the underlying PTY process (when available).",
"type": [
"string",
"null"
]
},
"source": {
"allOf": [
{
"$ref": "#/definitions/ExecCommandSource"
}
],
"default": "agent",
"description": "Where the command originated. Defaults to Agent for backward compatibility."
},
"turn_id": {
"description": "Turn ID that this command belongs to.",
"type": "string"
},
"type": {
"enum": [
"exec_command_begin"
],
"title": "ExecCommandBeginEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"command",
"cwd",
"parsed_cmd",
"turn_id",
"type"
],
"title": "ExecCommandBeginEventMsg",
"type": "object"
},
{
"description": "Incremental chunk of output from a running command.",
"properties": {
"call_id": {
"description": "Identifier for the ExecCommandBegin that produced this chunk.",
"type": "string"
},
"chunk": {
"description": "Raw bytes from the stream (may not be valid UTF-8).",
"type": "string"
},
"stream": {
"allOf": [
{
"$ref": "#/definitions/ExecOutputStream"
}
],
"description": "Which stream produced this chunk."
},
"type": {
"enum": [
"exec_command_output_delta"
],
"title": "ExecCommandOutputDeltaEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"chunk",
"stream",
"type"
],
"title": "ExecCommandOutputDeltaEventMsg",
"type": "object"
},
{
"description": "Terminal interaction for an in-progress command (stdin sent and stdout observed).",
"properties": {
"call_id": {
"description": "Identifier for the ExecCommandBegin that produced this chunk.",
"type": "string"
},
"process_id": {
"description": "Process id associated with the running command.",
"type": "string"
},
"stdin": {
"description": "Stdin sent to the running session.",
"type": "string"
},
"type": {
"enum": [
"terminal_interaction"
],
"title": "TerminalInteractionEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"process_id",
"stdin",
"type"
],
"title": "TerminalInteractionEventMsg",
"type": "object"
},
{
"properties": {
"aggregated_output": {
"default": "",
"description": "Captured aggregated output",
"type": "string"
},
"call_id": {
"description": "Identifier for the ExecCommandBegin that finished.",
"type": "string"
},
"command": {
"description": "The command that was executed.",
"items": {
"type": "string"
},
"type": "array"
},
"cwd": {
"description": "The command's working directory if not the default cwd for the agent.",
"type": "string"
},
"duration": {
"allOf": [
{
"$ref": "#/definitions/Duration"
}
],
"description": "The duration of the command execution."
},
"exit_code": {
"description": "The command's exit code.",
"format": "int32",
"type": "integer"
},
"formatted_output": {
"description": "Formatted output from the command, as seen by the model.",
"type": "string"
},
"interaction_input": {
"description": "Raw input sent to a unified exec session (if this is an interaction event).",
"type": [
"string",
"null"
]
},
"parsed_cmd": {
"items": {
"$ref": "#/definitions/ParsedCommand"
},
"type": "array"
},
"process_id": {
"description": "Identifier for the underlying PTY process (when available).",
"type": [
"string",
"null"
]
},
"source": {
"allOf": [
{
"$ref": "#/definitions/ExecCommandSource"
}
],
"default": "agent",
"description": "Where the command originated. Defaults to Agent for backward compatibility."
},
feat(app-server): experimental flag to persist extended history (#11227) This PR adds an experimental `persist_extended_history` bool flag to app-server thread APIs so rollout logs can retain a richer set of EventMsgs for non-lossy Thread > Turn > ThreadItems reconstruction (i.e. on `thread/resume`). ### Motivation Today, our rollout recorder only persists a small subset (e.g. user message, reasoning, assistant message) of `EventMsg` types, dropping a good number (like command exec, file change, etc.) that are important for reconstructing full item history for `thread/resume`, `thread/read`, and `thread/fork`. Some clients want to be able to resume a thread without lossiness. This lossiness is primarily a UI thing, since what the model sees are `ResponseItem` and not `EventMsg`. ### Approach This change introduces an opt-in `persist_full_history` flag to preserve those events when you start/resume/fork a thread (defaults to `false`). This is done by adding an `EventPersistenceMode` to the rollout recorder: - `Limited` (existing behavior, default) - `Extended` (new opt-in behavior) In `Extended` mode, persist additional `EventMsg` variants needed for non-lossy app-server `ThreadItem` reconstruction. We now store the following ThreadItems that we didn't before: - web search - command execution - patch/file changes - MCP tool calls - image view calls - collab tool outcomes - context compaction - review mode enter/exit For **command executions** in particular, we truncate the output using the existing `truncate_text` from core to store an upper bound of 10,000 bytes, which is also the default value for truncating tool outputs shown to the model. This keeps the size of the rollout file and command execution items returned over the wire reasonable. And we also persist `EventMsg::Error` which we can now map back to the Turn's status and populates the Turn's error metadata. #### Updates to EventMsgs To truly make `thread/resume` non-lossy, we also needed to persist the `status` on `EventMsg::CommandExecutionEndEvent` and `EventMsg::PatchApplyEndEvent`. Previously it was not obvious whether a command failed or was declined (similar for apply_patch). These EventMsgs were never persisted before so I made it a required field.
2026-02-12 11:34:22 -08:00
"status": {
"allOf": [
{
"$ref": "#/definitions/ExecCommandStatus"
}
],
"description": "Completion status for this command execution."
},
"stderr": {
"description": "Captured stderr",
"type": "string"
},
"stdout": {
"description": "Captured stdout",
"type": "string"
},
"turn_id": {
"description": "Turn ID that this command belongs to.",
"type": "string"
},
"type": {
"enum": [
"exec_command_end"
],
"title": "ExecCommandEndEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"command",
"cwd",
"duration",
"exit_code",
"formatted_output",
"parsed_cmd",
feat(app-server): experimental flag to persist extended history (#11227) This PR adds an experimental `persist_extended_history` bool flag to app-server thread APIs so rollout logs can retain a richer set of EventMsgs for non-lossy Thread > Turn > ThreadItems reconstruction (i.e. on `thread/resume`). ### Motivation Today, our rollout recorder only persists a small subset (e.g. user message, reasoning, assistant message) of `EventMsg` types, dropping a good number (like command exec, file change, etc.) that are important for reconstructing full item history for `thread/resume`, `thread/read`, and `thread/fork`. Some clients want to be able to resume a thread without lossiness. This lossiness is primarily a UI thing, since what the model sees are `ResponseItem` and not `EventMsg`. ### Approach This change introduces an opt-in `persist_full_history` flag to preserve those events when you start/resume/fork a thread (defaults to `false`). This is done by adding an `EventPersistenceMode` to the rollout recorder: - `Limited` (existing behavior, default) - `Extended` (new opt-in behavior) In `Extended` mode, persist additional `EventMsg` variants needed for non-lossy app-server `ThreadItem` reconstruction. We now store the following ThreadItems that we didn't before: - web search - command execution - patch/file changes - MCP tool calls - image view calls - collab tool outcomes - context compaction - review mode enter/exit For **command executions** in particular, we truncate the output using the existing `truncate_text` from core to store an upper bound of 10,000 bytes, which is also the default value for truncating tool outputs shown to the model. This keeps the size of the rollout file and command execution items returned over the wire reasonable. And we also persist `EventMsg::Error` which we can now map back to the Turn's status and populates the Turn's error metadata. #### Updates to EventMsgs To truly make `thread/resume` non-lossy, we also needed to persist the `status` on `EventMsg::CommandExecutionEndEvent` and `EventMsg::PatchApplyEndEvent`. Previously it was not obvious whether a command failed or was declined (similar for apply_patch). These EventMsgs were never persisted before so I made it a required field.
2026-02-12 11:34:22 -08:00
"status",
"stderr",
"stdout",
"turn_id",
"type"
],
"title": "ExecCommandEndEventMsg",
"type": "object"
},
{
"description": "Notification that the agent attached a local image via the view_image tool.",
"properties": {
"call_id": {
"description": "Identifier for the originating tool call.",
"type": "string"
},
"path": {
"description": "Local filesystem path provided to the tool.",
"type": "string"
},
"type": {
"enum": [
"view_image_tool_call"
],
"title": "ViewImageToolCallEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"path",
"type"
],
"title": "ViewImageToolCallEventMsg",
"type": "object"
},
{
"properties": {
"additional_permissions": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfile"
},
{
"type": "null"
}
],
"description": "Optional additional filesystem permissions requested for this command."
},
"approval_id": {
"description": "Identifier for this specific approval callback.\n\nWhen absent, the approval is for the command item itself (`call_id`). This is present for subcommand approvals (via execve intercept).",
"type": [
"string",
"null"
]
},
feat: include available decisions in command approval requests (#12758) Command-approval clients currently infer which choices to show from side-channel fields like `networkApprovalContext`, `proposedExecpolicyAmendment`, and `additionalPermissions`. That makes the request shape harder to evolve, and it forces each client to replicate the server's heuristics instead of receiving the exact decision list for the prompt. This PR introduces a mapping between `CommandExecutionApprovalDecision` and `codex_protocol::protocol::ReviewDecision`: ```rust impl From<CoreReviewDecision> for CommandExecutionApprovalDecision { fn from(value: CoreReviewDecision) -> Self { match value { CoreReviewDecision::Approved => Self::Accept, CoreReviewDecision::ApprovedExecpolicyAmendment { proposed_execpolicy_amendment, } => Self::AcceptWithExecpolicyAmendment { execpolicy_amendment: proposed_execpolicy_amendment.into(), }, CoreReviewDecision::ApprovedForSession => Self::AcceptForSession, CoreReviewDecision::NetworkPolicyAmendment { network_policy_amendment, } => Self::ApplyNetworkPolicyAmendment { network_policy_amendment: network_policy_amendment.into(), }, CoreReviewDecision::Abort => Self::Cancel, CoreReviewDecision::Denied => Self::Decline, } } } ``` And updates `CommandExecutionRequestApprovalParams` to have a new field: ```rust available_decisions: Option<Vec<CommandExecutionApprovalDecision>> ``` when, if specified, should make it easier for clients to display an appropriate list of options in the UI. This makes it possible for `CoreShellActionProvider::prompt()` in `unix_escalation.rs` to specify the `Vec<ReviewDecision>` directly, adding support for `ApprovedForSession` when approving a skill script, which was previously missing in the TUI. Note this results in a significant change to `exec_options()` in `approval_overlay.rs`, as the displayed options are now derived from `available_decisions: &[ReviewDecision]`. ## What Changed - Add `available_decisions` to [`ExecApprovalRequestEvent`](https://github.com/openai/codex/blob/de00e932dd9801de0a4faac0519162099753f331/codex-rs/protocol/src/approvals.rs#L111-L175), including helpers to derive the legacy default choices when older senders omit the field. - Map `codex_protocol::protocol::ReviewDecision` to app-server `CommandExecutionApprovalDecision` and expose the ordered list as experimental `availableDecisions` in [`CommandExecutionRequestApprovalParams`](https://github.com/openai/codex/blob/de00e932dd9801de0a4faac0519162099753f331/codex-rs/app-server-protocol/src/protocol/v2.rs#L3798-L3807). - Thread optional `available_decisions` through the core approval path so Unix shell escalation can explicitly request `ApprovedForSession` for session-scoped approvals instead of relying on client heuristics. [`unix_escalation.rs`](https://github.com/openai/codex/blob/de00e932dd9801de0a4faac0519162099753f331/codex-rs/core/src/tools/runtimes/shell/unix_escalation.rs#L194-L214) - Update the TUI approval overlay to build its buttons from the ordered decision list, while preserving the legacy fallback when `available_decisions` is missing. - Update the app-server README, test client output, and generated schema artifacts to document and surface the new field. ## Testing - Add `approval_overlay.rs` coverage for explicit decision lists, including the generic `ApprovedForSession` path and network approval options. - Update `chatwidget/tests.rs` and app-server protocol tests to populate the new optional field and keep older event shapes working. ## Developers Docs - If we document `item/commandExecution/requestApproval` on [developers.openai.com/codex](https://developers.openai.com/codex), add experimental `availableDecisions` as the preferred source of approval choices and note that older servers may omit it.
2026-02-25 17:10:46 -08:00
"available_decisions": {
"description": "Ordered list of decisions the client may present for this prompt.\n\nWhen absent, clients should derive the legacy default set from the other fields on this request.",
"items": {
"$ref": "#/definitions/ReviewDecision"
},
"type": [
"array",
"null"
]
},
"call_id": {
"description": "Identifier for the associated command execution item.",
"type": "string"
},
"command": {
"description": "The command to be executed.",
"items": {
"type": "string"
},
"type": "array"
},
"cwd": {
"description": "The command's working directory.",
"type": "string"
},
"network_approval_context": {
"anyOf": [
{
"$ref": "#/definitions/NetworkApprovalContext"
},
{
"type": "null"
}
],
"description": "Optional network context for a blocked request that can be approved."
},
"parsed_cmd": {
"items": {
"$ref": "#/definitions/ParsedCommand"
},
"type": "array"
},
"proposed_execpolicy_amendment": {
"description": "Proposed execpolicy amendment that can be applied to allow future runs.",
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
},
"proposed_network_policy_amendments": {
"description": "Proposed network policy amendments (for example allow/deny this host in future).",
"items": {
"$ref": "#/definitions/NetworkPolicyAmendment"
},
"type": [
"array",
"null"
]
},
"reason": {
"description": "Optional human-readable reason for the approval (e.g. retry without sandbox).",
"type": [
"string",
"null"
]
},
app-server: include experimental skill metadata in exec approval requests (#13929) ## Summary This change surfaces skill metadata on command approval requests so app-server clients can tell when an approval came from a skill script and identify the originating `SKILL.md`. - add `skill_metadata` to exec approval events in the shared protocol - thread skill metadata through core shell escalation and delegated approval handling for skill-triggered approvals - expose the field in app-server v2 as experimental `skillMetadata` - regenerate the JSON/TypeScript schemas and cover the new field in protocol, transport, core, and TUI tests ## Why Skill-triggered approvals already carry skill context inside core, but app-server clients could not see which skill caused the prompt. Sending the skill metadata with the approval request makes it possible for clients to present better approval UX and connect the prompt back to the relevant skill definition. ## example event in app-server-v2 verified that we see this event when experimental api is on: ``` < { < "id": 11, < "method": "item/commandExecution/requestApproval", < "params": { < "additionalPermissions": { < "fileSystem": null, < "macos": { < "accessibility": false, < "automations": { < "bundle_ids": [ < "com.apple.Notes" < ] < }, < "calendar": false, < "preferences": "read_only" < }, < "network": null < }, < "approvalId": "25d600ee-5a3c-4746-8d17-e2e61fb4c563", < "availableDecisions": [ < "accept", < "acceptForSession", < "cancel" < ], < "command": "/Applications/ChatGPT.app/Contents/Resources/CodexAppServer_CodexAppServerBundledSkills.bundle/Contents/Resources/skills/apple-notes/scripts/notes_info", < "commandActions": [ < { < "command": "/Applications/ChatGPT.app/Contents/Resources/CodexAppServer_CodexAppServerBundledSkills.bundle/Contents/Resources/skills/apple-notes/scripts/notes_info", < "type": "unknown" < } < ], < "cwd": "/Applications/ChatGPT.app/Contents/Resources/CodexAppServer_CodexAppServerBundledSkills.bundle/Contents/Resources/skills/apple-notes", < "itemId": "call_jZp3xFpNg4D8iKAD49cvEvZy", < "skillMetadata": { < "pathToSkillsMd": "/Applications/ChatGPT.app/Contents/Resources/CodexAppServer_CodexAppServerBundledSkills.bundle/Contents/Resources/skills/apple-notes/SKILL.md" < }, < "threadId": "019ccc10-b7d3-7ff2-84fe-3a75e7681e69", < "turnId": "019ccc10-b848-76f1-81b3-4a1fa225493f" < } < }` ``` & verified that this is the event when experimental api is off: ``` < { < "id": 13, < "method": "item/commandExecution/requestApproval", < "params": { < "approvalId": "5fbbf776-261b-4cf8-899b-c125b547f2c0", < "availableDecisions": [ < "accept", < "acceptForSession", < "cancel" < ], < "command": "/Applications/ChatGPT.app/Contents/Resources/CodexAppServer_CodexAppServerBundledSkills.bundle/Contents/Resources/skills/apple-notes/scripts/notes_info", < "commandActions": [ < { < "command": "/Applications/ChatGPT.app/Contents/Resources/CodexAppServer_CodexAppServerBundledSkills.bundle/Contents/Resources/skills/apple-notes/scripts/notes_info", < "type": "unknown" < } < ], < "cwd": "/Users/celia/code/codex/codex-rs", < "itemId": "call_OV2DHzTgYcbYtWaTTBWlocOt", < "threadId": "019ccc16-2a2b-7be1-8500-e00d45b892d4", < "turnId": "019ccc16-2a8e-7961-98ec-649600e7d06a" < } < } ```
2026-03-08 18:07:46 -07:00
"skill_metadata": {
"anyOf": [
{
"$ref": "#/definitions/ExecApprovalRequestSkillMetadata"
},
{
"type": "null"
}
],
"description": "Optional skill metadata when the approval was triggered by a skill script."
},
"turn_id": {
"default": "",
"description": "Turn ID that this command belongs to. Uses `#[serde(default)]` for backwards compatibility.",
"type": "string"
},
"type": {
"enum": [
"exec_approval_request"
],
"title": "ExecApprovalRequestEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"command",
"cwd",
"parsed_cmd",
"type"
],
"title": "ExecApprovalRequestEventMsg",
"type": "object"
},
{
"properties": {
"call_id": {
"description": "Responses API call id for the associated tool call, if available.",
"type": "string"
},
"permissions": {
"$ref": "#/definitions/PermissionProfile"
},
"reason": {
"type": [
"string",
"null"
]
},
"turn_id": {
"default": "",
"description": "Turn ID that this request belongs to. Uses `#[serde(default)]` for backwards compatibility.",
"type": "string"
},
"type": {
"enum": [
"request_permissions"
],
"title": "RequestPermissionsEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"permissions",
"type"
],
"title": "RequestPermissionsEventMsg",
"type": "object"
},
{
"properties": {
"call_id": {
"description": "Responses API call id for the associated tool call, if available.",
"type": "string"
},
"questions": {
"items": {
"$ref": "#/definitions/RequestUserInputQuestion"
},
"type": "array"
},
"turn_id": {
"default": "",
"description": "Turn ID that this request belongs to. Uses `#[serde(default)]` for backwards compatibility.",
"type": "string"
},
"type": {
"enum": [
"request_user_input"
],
"title": "RequestUserInputEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"questions",
"type"
],
"title": "RequestUserInputEventMsg",
"type": "object"
},
{
"properties": {
"arguments": true,
"callId": {
"type": "string"
},
"tool": {
"type": "string"
},
"turnId": {
"type": "string"
},
"type": {
"enum": [
"dynamic_tool_call_request"
],
"title": "DynamicToolCallRequestEventMsgType",
"type": "string"
}
},
"required": [
"arguments",
"callId",
"tool",
"turnId",
"type"
],
"title": "DynamicToolCallRequestEventMsg",
"type": "object"
},
{
"properties": {
"arguments": {
"description": "Dynamic tool call arguments."
},
"call_id": {
"description": "Identifier for the corresponding DynamicToolCallRequest.",
"type": "string"
},
"content_items": {
"description": "Dynamic tool response content items.",
"items": {
"$ref": "#/definitions/DynamicToolCallOutputContentItem"
},
"type": "array"
},
"duration": {
"allOf": [
{
"$ref": "#/definitions/Duration"
}
],
"description": "The duration of the dynamic tool call."
},
"error": {
"description": "Optional error text when the tool call failed before producing a response.",
"type": [
"string",
"null"
]
},
"success": {
"description": "Whether the tool call succeeded.",
"type": "boolean"
},
"tool": {
"description": "Dynamic tool name.",
"type": "string"
},
"turn_id": {
"description": "Turn ID that this dynamic tool call belongs to.",
"type": "string"
},
"type": {
"enum": [
"dynamic_tool_call_response"
],
"title": "DynamicToolCallResponseEventMsgType",
"type": "string"
}
},
"required": [
"arguments",
"call_id",
"content_items",
"duration",
"success",
"tool",
"turn_id",
"type"
],
"title": "DynamicToolCallResponseEventMsg",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
feat(app-server): support mcp elicitations in v2 api (#13425) This adds a first-class server request for MCP server elicitations: `mcpServer/elicitation/request`. Until now, MCP elicitation requests only showed up as a raw `codex/event/elicitation_request` event from core. That made it hard for v2 clients to handle elicitations using the same request/response flow as other server-driven interactions (like shell and `apply_patch` tools). This also updates the underlying MCP elicitation request handling in core to pass through the full MCP request (including URL and form data) so we can expose it properly in app-server. ### Why not `item/mcpToolCall/elicitationRequest`? This is because MCP elicitations are related to MCP servers first, and only optionally to a specific MCP tool call. In the MCP protocol, elicitation is a server-to-client capability: the server sends `elicitation/create`, and the client replies with an elicitation result. RMCP models it that way as well. In practice an elicitation is often triggered by an MCP tool call, but not always. ### What changed - add `mcpServer/elicitation/request` to the v2 app-server API - translate core `codex/event/elicitation_request` events into the new v2 server request - map client responses back into `Op::ResolveElicitation` so the MCP server can continue - update app-server docs and generated protocol schema - add an end-to-end app-server test that covers the full round trip through a real RMCP elicitation flow - The new test exercises a realistic case where an MCP tool call triggers an elicitation, the app-server emits mcpServer/elicitation/request, the client accepts it, and the tool call resumes and completes successfully. ### app-server API flow - Client starts a thread with `thread/start`. - Client starts a turn with `turn/start`. - App-server sends `item/started` for the `mcpToolCall`. - While that tool call is in progress, app-server sends `mcpServer/elicitation/request`. - Client responds to that request with `{ action: "accept" | "decline" | "cancel" }`. - App-server sends `serverRequest/resolved`. - App-server sends `item/completed` for the mcpToolCall. - App-server sends `turn/completed`. - If the turn is interrupted while the elicitation is pending, app-server still sends `serverRequest/resolved` before the turn finishes.
2026-03-05 07:20:20 -08:00
"request": {
"$ref": "#/definitions/ElicitationRequest"
},
"server_name": {
"type": "string"
},
"turn_id": {
"description": "Turn ID that this elicitation belongs to, when known.",
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"elicitation_request"
],
"title": "ElicitationRequestEventMsgType",
"type": "string"
}
},
"required": [
"id",
feat(app-server): support mcp elicitations in v2 api (#13425) This adds a first-class server request for MCP server elicitations: `mcpServer/elicitation/request`. Until now, MCP elicitation requests only showed up as a raw `codex/event/elicitation_request` event from core. That made it hard for v2 clients to handle elicitations using the same request/response flow as other server-driven interactions (like shell and `apply_patch` tools). This also updates the underlying MCP elicitation request handling in core to pass through the full MCP request (including URL and form data) so we can expose it properly in app-server. ### Why not `item/mcpToolCall/elicitationRequest`? This is because MCP elicitations are related to MCP servers first, and only optionally to a specific MCP tool call. In the MCP protocol, elicitation is a server-to-client capability: the server sends `elicitation/create`, and the client replies with an elicitation result. RMCP models it that way as well. In practice an elicitation is often triggered by an MCP tool call, but not always. ### What changed - add `mcpServer/elicitation/request` to the v2 app-server API - translate core `codex/event/elicitation_request` events into the new v2 server request - map client responses back into `Op::ResolveElicitation` so the MCP server can continue - update app-server docs and generated protocol schema - add an end-to-end app-server test that covers the full round trip through a real RMCP elicitation flow - The new test exercises a realistic case where an MCP tool call triggers an elicitation, the app-server emits mcpServer/elicitation/request, the client accepts it, and the tool call resumes and completes successfully. ### app-server API flow - Client starts a thread with `thread/start`. - Client starts a turn with `turn/start`. - App-server sends `item/started` for the `mcpToolCall`. - While that tool call is in progress, app-server sends `mcpServer/elicitation/request`. - Client responds to that request with `{ action: "accept" | "decline" | "cancel" }`. - App-server sends `serverRequest/resolved`. - App-server sends `item/completed` for the mcpToolCall. - App-server sends `turn/completed`. - If the turn is interrupted while the elicitation is pending, app-server still sends `serverRequest/resolved` before the turn finishes.
2026-03-05 07:20:20 -08:00
"request",
"server_name",
"type"
],
"title": "ElicitationRequestEventMsg",
"type": "object"
},
{
"properties": {
"call_id": {
"description": "Responses API call id for the associated patch apply call, if available.",
"type": "string"
},
"changes": {
"additionalProperties": {
"$ref": "#/definitions/FileChange"
},
"type": "object"
},
"grant_root": {
"description": "When set, the agent is asking the user to allow writes under this root for the remainder of the session.",
"type": [
"string",
"null"
]
},
"reason": {
"description": "Optional explanatory reason (e.g. request for extra write access).",
"type": [
"string",
"null"
]
},
"turn_id": {
"default": "",
"description": "Turn ID that this patch belongs to. Uses `#[serde(default)]` for backwards compatibility with older senders.",
"type": "string"
},
"type": {
"enum": [
"apply_patch_approval_request"
],
"title": "ApplyPatchApprovalRequestEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"changes",
"type"
],
"title": "ApplyPatchApprovalRequestEventMsg",
"type": "object"
},
{
"description": "Notification advising the user that something they are using has been deprecated and should be phased out.",
"properties": {
"details": {
"description": "Optional extra guidance, such as migration steps or rationale.",
"type": [
"string",
"null"
]
},
"summary": {
"description": "Concise summary of what is deprecated.",
"type": "string"
},
"type": {
"enum": [
"deprecation_notice"
],
"title": "DeprecationNoticeEventMsgType",
"type": "string"
}
},
"required": [
"summary",
"type"
],
"title": "DeprecationNoticeEventMsg",
"type": "object"
},
{
"properties": {
"message": {
"type": "string"
},
"type": {
"enum": [
"background_event"
],
"title": "BackgroundEventEventMsgType",
"type": "string"
}
},
"required": [
"message",
"type"
],
"title": "BackgroundEventEventMsg",
"type": "object"
},
{
"properties": {
"message": {
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"undo_started"
],
"title": "UndoStartedEventMsgType",
"type": "string"
}
},
"required": [
"type"
],
"title": "UndoStartedEventMsg",
"type": "object"
},
{
"properties": {
"message": {
"type": [
"string",
"null"
]
},
"success": {
"type": "boolean"
},
"type": {
"enum": [
"undo_completed"
],
"title": "UndoCompletedEventMsgType",
"type": "string"
}
},
"required": [
"success",
"type"
],
"title": "UndoCompletedEventMsg",
"type": "object"
},
{
"description": "Notification that a model stream experienced an error or disconnect and the system is handling it (e.g., retrying with backoff).",
"properties": {
"additional_details": {
"default": null,
"description": "Optional details about the underlying stream failure (often the same human-readable message that is surfaced as the terminal error if retries are exhausted).",
"type": [
"string",
"null"
]
},
"codex_error_info": {
"anyOf": [
{
"$ref": "#/definitions/CodexErrorInfo"
},
{
"type": "null"
}
],
"default": null
},
"message": {
"type": "string"
},
"type": {
"enum": [
"stream_error"
],
"title": "StreamErrorEventMsgType",
"type": "string"
}
},
"required": [
"message",
"type"
],
"title": "StreamErrorEventMsg",
"type": "object"
},
{
"description": "Notification that the agent is about to apply a code patch. Mirrors `ExecCommandBegin` so frontends can show progress indicators.",
"properties": {
"auto_approved": {
"description": "If true, there was no ApplyPatchApprovalRequest for this patch.",
"type": "boolean"
},
"call_id": {
"description": "Identifier so this can be paired with the PatchApplyEnd event.",
"type": "string"
},
"changes": {
"additionalProperties": {
"$ref": "#/definitions/FileChange"
},
"description": "The changes to be applied.",
"type": "object"
},
"turn_id": {
"default": "",
"description": "Turn ID that this patch belongs to. Uses `#[serde(default)]` for backwards compatibility.",
"type": "string"
},
"type": {
"enum": [
"patch_apply_begin"
],
"title": "PatchApplyBeginEventMsgType",
"type": "string"
}
},
"required": [
"auto_approved",
"call_id",
"changes",
"type"
],
"title": "PatchApplyBeginEventMsg",
"type": "object"
},
{
"description": "Notification that a patch application has finished.",
"properties": {
"call_id": {
"description": "Identifier for the PatchApplyBegin that finished.",
"type": "string"
},
"changes": {
"additionalProperties": {
"$ref": "#/definitions/FileChange"
},
"default": {},
"description": "The changes that were applied (mirrors PatchApplyBeginEvent::changes).",
"type": "object"
},
feat(app-server): experimental flag to persist extended history (#11227) This PR adds an experimental `persist_extended_history` bool flag to app-server thread APIs so rollout logs can retain a richer set of EventMsgs for non-lossy Thread > Turn > ThreadItems reconstruction (i.e. on `thread/resume`). ### Motivation Today, our rollout recorder only persists a small subset (e.g. user message, reasoning, assistant message) of `EventMsg` types, dropping a good number (like command exec, file change, etc.) that are important for reconstructing full item history for `thread/resume`, `thread/read`, and `thread/fork`. Some clients want to be able to resume a thread without lossiness. This lossiness is primarily a UI thing, since what the model sees are `ResponseItem` and not `EventMsg`. ### Approach This change introduces an opt-in `persist_full_history` flag to preserve those events when you start/resume/fork a thread (defaults to `false`). This is done by adding an `EventPersistenceMode` to the rollout recorder: - `Limited` (existing behavior, default) - `Extended` (new opt-in behavior) In `Extended` mode, persist additional `EventMsg` variants needed for non-lossy app-server `ThreadItem` reconstruction. We now store the following ThreadItems that we didn't before: - web search - command execution - patch/file changes - MCP tool calls - image view calls - collab tool outcomes - context compaction - review mode enter/exit For **command executions** in particular, we truncate the output using the existing `truncate_text` from core to store an upper bound of 10,000 bytes, which is also the default value for truncating tool outputs shown to the model. This keeps the size of the rollout file and command execution items returned over the wire reasonable. And we also persist `EventMsg::Error` which we can now map back to the Turn's status and populates the Turn's error metadata. #### Updates to EventMsgs To truly make `thread/resume` non-lossy, we also needed to persist the `status` on `EventMsg::CommandExecutionEndEvent` and `EventMsg::PatchApplyEndEvent`. Previously it was not obvious whether a command failed or was declined (similar for apply_patch). These EventMsgs were never persisted before so I made it a required field.
2026-02-12 11:34:22 -08:00
"status": {
"allOf": [
{
"$ref": "#/definitions/PatchApplyStatus"
}
],
"description": "Completion status for this patch application."
},
"stderr": {
"description": "Captured stderr (parser errors, IO failures, etc.).",
"type": "string"
},
"stdout": {
"description": "Captured stdout (summary printed by apply_patch).",
"type": "string"
},
"success": {
"description": "Whether the patch was applied successfully.",
"type": "boolean"
},
"turn_id": {
"default": "",
"description": "Turn ID that this patch belongs to. Uses `#[serde(default)]` for backwards compatibility.",
"type": "string"
},
"type": {
"enum": [
"patch_apply_end"
],
"title": "PatchApplyEndEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
feat(app-server): experimental flag to persist extended history (#11227) This PR adds an experimental `persist_extended_history` bool flag to app-server thread APIs so rollout logs can retain a richer set of EventMsgs for non-lossy Thread > Turn > ThreadItems reconstruction (i.e. on `thread/resume`). ### Motivation Today, our rollout recorder only persists a small subset (e.g. user message, reasoning, assistant message) of `EventMsg` types, dropping a good number (like command exec, file change, etc.) that are important for reconstructing full item history for `thread/resume`, `thread/read`, and `thread/fork`. Some clients want to be able to resume a thread without lossiness. This lossiness is primarily a UI thing, since what the model sees are `ResponseItem` and not `EventMsg`. ### Approach This change introduces an opt-in `persist_full_history` flag to preserve those events when you start/resume/fork a thread (defaults to `false`). This is done by adding an `EventPersistenceMode` to the rollout recorder: - `Limited` (existing behavior, default) - `Extended` (new opt-in behavior) In `Extended` mode, persist additional `EventMsg` variants needed for non-lossy app-server `ThreadItem` reconstruction. We now store the following ThreadItems that we didn't before: - web search - command execution - patch/file changes - MCP tool calls - image view calls - collab tool outcomes - context compaction - review mode enter/exit For **command executions** in particular, we truncate the output using the existing `truncate_text` from core to store an upper bound of 10,000 bytes, which is also the default value for truncating tool outputs shown to the model. This keeps the size of the rollout file and command execution items returned over the wire reasonable. And we also persist `EventMsg::Error` which we can now map back to the Turn's status and populates the Turn's error metadata. #### Updates to EventMsgs To truly make `thread/resume` non-lossy, we also needed to persist the `status` on `EventMsg::CommandExecutionEndEvent` and `EventMsg::PatchApplyEndEvent`. Previously it was not obvious whether a command failed or was declined (similar for apply_patch). These EventMsgs were never persisted before so I made it a required field.
2026-02-12 11:34:22 -08:00
"status",
"stderr",
"stdout",
"success",
"type"
],
"title": "PatchApplyEndEventMsg",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"turn_diff"
],
"title": "TurnDiffEventMsgType",
"type": "string"
},
"unified_diff": {
"type": "string"
}
},
"required": [
"type",
"unified_diff"
],
"title": "TurnDiffEventMsg",
"type": "object"
},
{
"description": "Response to GetHistoryEntryRequest.",
"properties": {
"entry": {
"anyOf": [
{
"$ref": "#/definitions/HistoryEntry"
},
{
"type": "null"
}
],
"description": "The entry at the requested offset, if available and parseable."
},
"log_id": {
"format": "uint64",
"minimum": 0.0,
"type": "integer"
},
"offset": {
"format": "uint",
"minimum": 0.0,
"type": "integer"
},
"type": {
"enum": [
"get_history_entry_response"
],
"title": "GetHistoryEntryResponseEventMsgType",
"type": "string"
}
},
"required": [
"log_id",
"offset",
"type"
],
"title": "GetHistoryEntryResponseEventMsg",
"type": "object"
},
{
"description": "List of MCP tools available to the agent.",
"properties": {
"auth_statuses": {
"additionalProperties": {
"$ref": "#/definitions/McpAuthStatus"
},
"description": "Authentication status for each configured MCP server.",
"type": "object"
},
"resource_templates": {
"additionalProperties": {
"items": {
"$ref": "#/definitions/ResourceTemplate"
},
"type": "array"
},
"description": "Known resource templates grouped by server name.",
"type": "object"
},
"resources": {
"additionalProperties": {
"items": {
"$ref": "#/definitions/Resource"
},
"type": "array"
},
"description": "Known resources grouped by server name.",
"type": "object"
},
"tools": {
"additionalProperties": {
"$ref": "#/definitions/Tool"
},
"description": "Fully qualified tool name -> tool definition.",
"type": "object"
},
"type": {
"enum": [
"mcp_list_tools_response"
],
"title": "McpListToolsResponseEventMsgType",
"type": "string"
}
},
"required": [
"auth_statuses",
"resource_templates",
"resources",
"tools",
"type"
],
"title": "McpListToolsResponseEventMsg",
"type": "object"
},
{
"description": "List of custom prompts available to the agent.",
"properties": {
"custom_prompts": {
"items": {
"$ref": "#/definitions/CustomPrompt"
},
"type": "array"
},
"type": {
"enum": [
"list_custom_prompts_response"
],
"title": "ListCustomPromptsResponseEventMsgType",
"type": "string"
}
},
"required": [
"custom_prompts",
"type"
],
"title": "ListCustomPromptsResponseEventMsg",
"type": "object"
},
{
"description": "List of skills available to the agent.",
"properties": {
"skills": {
"items": {
"$ref": "#/definitions/SkillsListEntry"
},
"type": "array"
},
"type": {
"enum": [
"list_skills_response"
],
"title": "ListSkillsResponseEventMsgType",
"type": "string"
}
},
"required": [
"skills",
"type"
],
"title": "ListSkillsResponseEventMsg",
"type": "object"
},
{
"description": "List of remote skills available to the agent.",
"properties": {
"skills": {
"items": {
"$ref": "#/definitions/RemoteSkillSummary"
},
"type": "array"
},
"type": {
"enum": [
"list_remote_skills_response"
],
"title": "ListRemoteSkillsResponseEventMsgType",
"type": "string"
}
},
"required": [
"skills",
"type"
],
"title": "ListRemoteSkillsResponseEventMsg",
"type": "object"
},
{
"description": "Remote skill downloaded to local cache.",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"path": {
"type": "string"
},
"type": {
"enum": [
"remote_skill_downloaded"
],
"title": "RemoteSkillDownloadedEventMsgType",
"type": "string"
}
},
"required": [
"id",
"name",
"path",
"type"
],
"title": "RemoteSkillDownloadedEventMsg",
"type": "object"
},
{
"description": "Notification that skill data may have been updated and clients may want to reload.",
"properties": {
"type": {
"enum": [
"skills_update_available"
],
"title": "SkillsUpdateAvailableEventMsgType",
"type": "string"
}
},
"required": [
"type"
],
"title": "SkillsUpdateAvailableEventMsg",
"type": "object"
},
{
"properties": {
"explanation": {
"default": null,
"description": "Arguments for the `update_plan` todo/checklist tool (not plan mode).",
"type": [
"string",
"null"
]
},
"plan": {
"items": {
"$ref": "#/definitions/PlanItemArg"
},
"type": "array"
},
"type": {
"enum": [
"plan_update"
],
"title": "PlanUpdateEventMsgType",
"type": "string"
}
},
"required": [
"plan",
"type"
],
"title": "PlanUpdateEventMsg",
"type": "object"
},
{
"properties": {
"reason": {
"$ref": "#/definitions/TurnAbortReason"
},
chore: persist turn_id in rollout session and make turn_id uuid based (#11246) Problem: 1. turn id is constructed in-memory; 2. on resuming threads, turn_id might not be unique; 3. client cannot no the boundary of a turn from rollout files easily. This PR does three things: 1. persist `task_started` and `task_complete` events; 1. persist `turn_id` in rollout turn events; 5. generate turn_id as unique uuids instead of incrementing it in memory. This helps us resolve the issue of clients wanting to have unique turn ids for resuming a thread, and knowing the boundry of each turn in rollout files. example debug logs ``` 2026-02-11T00:32:10.746876Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=8 turn=Turn { id: "019c4a07-d809-74c3-bc4b-fd9618487b4b", items: [UserMessage { id: "item-24", content: [Text { text: "hi", text_elements: [] }] }, AgentMessage { id: "item-25", text: "Hi. I’m in the workspace with your current changes loaded and ready. Send the next task and I’ll execute it end-to-end." }], status: Completed, error: None } 2026-02-11T00:32:10.746888Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=9 turn=Turn { id: "019c4a18-1004-76c0-a0fb-a77610f6a9b8", items: [UserMessage { id: "item-26", content: [Text { text: "hello", text_elements: [] }] }, AgentMessage { id: "item-27", text: "Hello. Ready for the next change in `codex-rs`; I can continue from the current in-progress diff or start a new task." }], status: Completed, error: None } 2026-02-11T00:32:10.746899Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=10 turn=Turn { id: "019c4a19-41f0-7db0-ad78-74f1503baeb8", items: [UserMessage { id: "item-28", content: [Text { text: "hello", text_elements: [] }] }, AgentMessage { id: "item-29", text: "Hello. Send the specific change you want in `codex-rs`, and I’ll implement it and run the required checks." }], status: Completed, error: None } ``` backward compatibility: if you try to resume an old session without task_started and task_complete event populated, the following happens: - If you resume and do nothing: those reconstructed historical IDs can differ next time you resume. - If you resume and send a new turn: the new turn gets a fresh UUID from live submission flow and is persisted, so that new turn’s ID is stable on later resumes. I think this behavior is fine, because we only care about deterministic turn id once a turn is triggered.
2026-02-10 19:56:01 -08:00
"turn_id": {
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"turn_aborted"
],
"title": "TurnAbortedEventMsgType",
"type": "string"
}
},
"required": [
"reason",
"type"
],
"title": "TurnAbortedEventMsg",
"type": "object"
},
{
"description": "Notification that the agent is shutting down.",
"properties": {
"type": {
"enum": [
"shutdown_complete"
],
"title": "ShutdownCompleteEventMsgType",
"type": "string"
}
},
"required": [
"type"
],
"title": "ShutdownCompleteEventMsg",
"type": "object"
},
{
"description": "Entered review mode.",
"properties": {
"target": {
"$ref": "#/definitions/ReviewTarget"
},
"type": {
"enum": [
"entered_review_mode"
],
"title": "EnteredReviewModeEventMsgType",
"type": "string"
},
"user_facing_hint": {
"type": [
"string",
"null"
]
}
},
"required": [
"target",
"type"
],
"title": "EnteredReviewModeEventMsg",
"type": "object"
},
{
"description": "Exited review mode with an optional final result to apply.",
"properties": {
"review_output": {
"anyOf": [
{
"$ref": "#/definitions/ReviewOutputEvent"
},
{
"type": "null"
}
]
},
"type": {
"enum": [
"exited_review_mode"
],
"title": "ExitedReviewModeEventMsgType",
"type": "string"
}
},
"required": [
"type"
],
"title": "ExitedReviewModeEventMsg",
"type": "object"
},
{
"properties": {
"item": {
"$ref": "#/definitions/ResponseItem"
},
"type": {
"enum": [
"raw_response_item"
],
"title": "RawResponseItemEventMsgType",
"type": "string"
}
},
"required": [
"item",
"type"
],
"title": "RawResponseItemEventMsg",
"type": "object"
},
{
"properties": {
"item": {
"$ref": "#/definitions/TurnItem"
},
"thread_id": {
"$ref": "#/definitions/ThreadId"
},
"turn_id": {
"type": "string"
},
"type": {
"enum": [
"item_started"
],
"title": "ItemStartedEventMsgType",
"type": "string"
}
},
"required": [
"item",
"thread_id",
"turn_id",
"type"
],
"title": "ItemStartedEventMsg",
"type": "object"
},
{
"properties": {
"item": {
"$ref": "#/definitions/TurnItem"
},
"thread_id": {
"$ref": "#/definitions/ThreadId"
},
"turn_id": {
"type": "string"
},
"type": {
"enum": [
"item_completed"
],
"title": "ItemCompletedEventMsgType",
"type": "string"
}
},
"required": [
"item",
"thread_id",
"turn_id",
"type"
],
"title": "ItemCompletedEventMsg",
"type": "object"
},
{
"properties": {
"run": {
"$ref": "#/definitions/HookRunSummary"
},
"turn_id": {
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"hook_started"
],
"title": "HookStartedEventMsgType",
"type": "string"
}
},
"required": [
"run",
"type"
],
"title": "HookStartedEventMsg",
"type": "object"
},
{
"properties": {
"run": {
"$ref": "#/definitions/HookRunSummary"
},
"turn_id": {
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"hook_completed"
],
"title": "HookCompletedEventMsgType",
"type": "string"
}
},
"required": [
"run",
"type"
],
"title": "HookCompletedEventMsg",
"type": "object"
},
{
"properties": {
"delta": {
"type": "string"
},
"item_id": {
"type": "string"
},
"thread_id": {
"type": "string"
},
"turn_id": {
"type": "string"
},
"type": {
"enum": [
"agent_message_content_delta"
],
"title": "AgentMessageContentDeltaEventMsgType",
"type": "string"
}
},
"required": [
"delta",
"item_id",
"thread_id",
"turn_id",
"type"
],
"title": "AgentMessageContentDeltaEventMsg",
"type": "object"
},
{
"properties": {
"delta": {
"type": "string"
},
"item_id": {
"type": "string"
},
"thread_id": {
"type": "string"
},
"turn_id": {
"type": "string"
},
"type": {
"enum": [
"plan_delta"
],
"title": "PlanDeltaEventMsgType",
"type": "string"
}
},
"required": [
"delta",
"item_id",
"thread_id",
"turn_id",
"type"
],
"title": "PlanDeltaEventMsg",
"type": "object"
},
{
"properties": {
"delta": {
"type": "string"
},
"item_id": {
"type": "string"
},
"summary_index": {
"default": 0,
"format": "int64",
"type": "integer"
},
"thread_id": {
"type": "string"
},
"turn_id": {
"type": "string"
},
"type": {
"enum": [
"reasoning_content_delta"
],
"title": "ReasoningContentDeltaEventMsgType",
"type": "string"
}
},
"required": [
"delta",
"item_id",
"thread_id",
"turn_id",
"type"
],
"title": "ReasoningContentDeltaEventMsg",
"type": "object"
},
{
"properties": {
"content_index": {
"default": 0,
"format": "int64",
"type": "integer"
},
"delta": {
"type": "string"
},
"item_id": {
"type": "string"
},
"thread_id": {
"type": "string"
},
"turn_id": {
"type": "string"
},
"type": {
"enum": [
"reasoning_raw_content_delta"
],
"title": "ReasoningRawContentDeltaEventMsgType",
"type": "string"
}
},
"required": [
"delta",
"item_id",
"thread_id",
"turn_id",
"type"
],
"title": "ReasoningRawContentDeltaEventMsg",
"type": "object"
},
{
"description": "Collab interaction: agent spawn begin.",
"properties": {
"call_id": {
"description": "Identifier for the collab tool call.",
"type": "string"
},
"model": {
"type": "string"
},
"prompt": {
"description": "Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the beginning.",
"type": "string"
},
"reasoning_effort": {
"$ref": "#/definitions/ReasoningEffort"
},
"sender_thread_id": {
"allOf": [
{
"$ref": "#/definitions/ThreadId"
}
],
"description": "Thread ID of the sender."
},
"type": {
"enum": [
"collab_agent_spawn_begin"
],
"title": "CollabAgentSpawnBeginEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"model",
"prompt",
"reasoning_effort",
"sender_thread_id",
"type"
],
"title": "CollabAgentSpawnBeginEventMsg",
"type": "object"
},
{
"description": "Collab interaction: agent spawn end.",
"properties": {
"call_id": {
"description": "Identifier for the collab tool call.",
"type": "string"
},
"new_agent_nickname": {
"description": "Optional nickname assigned to the new agent.",
"type": [
"string",
"null"
]
},
"new_agent_role": {
"description": "Optional role assigned to the new agent.",
"type": [
"string",
"null"
]
},
"new_thread_id": {
"anyOf": [
{
"$ref": "#/definitions/ThreadId"
},
{
"type": "null"
}
],
"description": "Thread ID of the newly spawned agent, if it was created."
},
"prompt": {
"description": "Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the beginning.",
"type": "string"
},
"sender_thread_id": {
"allOf": [
{
"$ref": "#/definitions/ThreadId"
}
],
"description": "Thread ID of the sender."
},
"status": {
"allOf": [
{
"$ref": "#/definitions/AgentStatus"
}
],
"description": "Last known status of the new agent reported to the sender agent."
},
"type": {
"enum": [
"collab_agent_spawn_end"
],
"title": "CollabAgentSpawnEndEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"prompt",
"sender_thread_id",
"status",
"type"
],
"title": "CollabAgentSpawnEndEventMsg",
"type": "object"
},
{
"description": "Collab interaction: agent interaction begin.",
"properties": {
"call_id": {
"description": "Identifier for the collab tool call.",
"type": "string"
},
"prompt": {
"description": "Prompt sent from the sender to the receiver. Can be empty to prevent CoT leaking at the beginning.",
"type": "string"
},
"receiver_thread_id": {
"allOf": [
{
"$ref": "#/definitions/ThreadId"
}
],
"description": "Thread ID of the receiver."
},
"sender_thread_id": {
"allOf": [
{
"$ref": "#/definitions/ThreadId"
}
],
"description": "Thread ID of the sender."
},
"type": {
"enum": [
"collab_agent_interaction_begin"
],
"title": "CollabAgentInteractionBeginEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"prompt",
"receiver_thread_id",
"sender_thread_id",
"type"
],
"title": "CollabAgentInteractionBeginEventMsg",
"type": "object"
},
{
"description": "Collab interaction: agent interaction end.",
"properties": {
"call_id": {
"description": "Identifier for the collab tool call.",
"type": "string"
},
"prompt": {
"description": "Prompt sent from the sender to the receiver. Can be empty to prevent CoT leaking at the beginning.",
"type": "string"
},
"receiver_agent_nickname": {
"description": "Optional nickname assigned to the receiver agent.",
"type": [
"string",
"null"
]
},
"receiver_agent_role": {
"description": "Optional role assigned to the receiver agent.",
"type": [
"string",
"null"
]
},
"receiver_thread_id": {
"allOf": [
{
"$ref": "#/definitions/ThreadId"
}
],
"description": "Thread ID of the receiver."
},
"sender_thread_id": {
"allOf": [
{
"$ref": "#/definitions/ThreadId"
}
],
"description": "Thread ID of the sender."
},
"status": {
"allOf": [
{
"$ref": "#/definitions/AgentStatus"
}
],
"description": "Last known status of the receiver agent reported to the sender agent."
},
"type": {
"enum": [
"collab_agent_interaction_end"
],
"title": "CollabAgentInteractionEndEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"prompt",
"receiver_thread_id",
"sender_thread_id",
"status",
"type"
],
"title": "CollabAgentInteractionEndEventMsg",
"type": "object"
},
{
"description": "Collab interaction: waiting begin.",
"properties": {
"call_id": {
"description": "ID of the waiting call.",
"type": "string"
},
"receiver_agents": {
"description": "Optional nicknames/roles for receivers.",
"items": {
"$ref": "#/definitions/CollabAgentRef"
},
"type": "array"
},
"receiver_thread_ids": {
"description": "Thread ID of the receivers.",
"items": {
"$ref": "#/definitions/ThreadId"
},
"type": "array"
},
"sender_thread_id": {
"allOf": [
{
"$ref": "#/definitions/ThreadId"
}
],
"description": "Thread ID of the sender."
},
"type": {
"enum": [
"collab_waiting_begin"
],
"title": "CollabWaitingBeginEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"receiver_thread_ids",
"sender_thread_id",
"type"
],
"title": "CollabWaitingBeginEventMsg",
"type": "object"
},
{
"description": "Collab interaction: waiting end.",
"properties": {
"agent_statuses": {
"description": "Optional receiver metadata paired with final statuses.",
"items": {
"$ref": "#/definitions/CollabAgentStatusEntry"
},
"type": "array"
},
"call_id": {
"description": "ID of the waiting call.",
"type": "string"
},
"sender_thread_id": {
"allOf": [
{
"$ref": "#/definitions/ThreadId"
}
],
"description": "Thread ID of the sender."
},
"statuses": {
"additionalProperties": {
"$ref": "#/definitions/AgentStatus"
},
"description": "Last known status of the receiver agents reported to the sender agent.",
"type": "object"
},
"type": {
"enum": [
"collab_waiting_end"
],
"title": "CollabWaitingEndEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"sender_thread_id",
"statuses",
"type"
],
"title": "CollabWaitingEndEventMsg",
"type": "object"
},
{
"description": "Collab interaction: close begin.",
"properties": {
"call_id": {
"description": "Identifier for the collab tool call.",
"type": "string"
},
"receiver_thread_id": {
"allOf": [
{
"$ref": "#/definitions/ThreadId"
}
],
"description": "Thread ID of the receiver."
},
"sender_thread_id": {
"allOf": [
{
"$ref": "#/definitions/ThreadId"
}
],
"description": "Thread ID of the sender."
},
"type": {
"enum": [
"collab_close_begin"
],
"title": "CollabCloseBeginEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"receiver_thread_id",
"sender_thread_id",
"type"
],
"title": "CollabCloseBeginEventMsg",
"type": "object"
},
{
"description": "Collab interaction: close end.",
"properties": {
"call_id": {
"description": "Identifier for the collab tool call.",
"type": "string"
},
"receiver_agent_nickname": {
"description": "Optional nickname assigned to the receiver agent.",
"type": [
"string",
"null"
]
},
"receiver_agent_role": {
"description": "Optional role assigned to the receiver agent.",
"type": [
"string",
"null"
]
},
"receiver_thread_id": {
"allOf": [
{
"$ref": "#/definitions/ThreadId"
}
],
"description": "Thread ID of the receiver."
},
"sender_thread_id": {
"allOf": [
{
"$ref": "#/definitions/ThreadId"
}
],
"description": "Thread ID of the sender."
},
"status": {
"allOf": [
{
"$ref": "#/definitions/AgentStatus"
}
],
"description": "Last known status of the receiver agent reported to the sender agent before the close."
},
"type": {
"enum": [
"collab_close_end"
],
"title": "CollabCloseEndEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"receiver_thread_id",
"sender_thread_id",
"status",
"type"
],
"title": "CollabCloseEndEventMsg",
"type": "object"
},
{
"description": "Collab interaction: resume begin.",
"properties": {
"call_id": {
"description": "Identifier for the collab tool call.",
"type": "string"
},
"receiver_agent_nickname": {
"description": "Optional nickname assigned to the receiver agent.",
"type": [
"string",
"null"
]
},
"receiver_agent_role": {
"description": "Optional role assigned to the receiver agent.",
"type": [
"string",
"null"
]
},
"receiver_thread_id": {
"allOf": [
{
"$ref": "#/definitions/ThreadId"
}
],
"description": "Thread ID of the receiver."
},
"sender_thread_id": {
"allOf": [
{
"$ref": "#/definitions/ThreadId"
}
],
"description": "Thread ID of the sender."
},
"type": {
"enum": [
"collab_resume_begin"
],
"title": "CollabResumeBeginEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"receiver_thread_id",
"sender_thread_id",
"type"
],
"title": "CollabResumeBeginEventMsg",
"type": "object"
},
{
"description": "Collab interaction: resume end.",
"properties": {
"call_id": {
"description": "Identifier for the collab tool call.",
"type": "string"
},
"receiver_agent_nickname": {
"description": "Optional nickname assigned to the receiver agent.",
"type": [
"string",
"null"
]
},
"receiver_agent_role": {
"description": "Optional role assigned to the receiver agent.",
"type": [
"string",
"null"
]
},
"receiver_thread_id": {
"allOf": [
{
"$ref": "#/definitions/ThreadId"
}
],
"description": "Thread ID of the receiver."
},
"sender_thread_id": {
"allOf": [
{
"$ref": "#/definitions/ThreadId"
}
],
"description": "Thread ID of the sender."
},
"status": {
"allOf": [
{
"$ref": "#/definitions/AgentStatus"
}
],
"description": "Last known status of the receiver agent reported to the sender agent after resume."
},
"type": {
"enum": [
"collab_resume_end"
],
"title": "CollabResumeEndEventMsgType",
"type": "string"
}
},
"required": [
"call_id",
"receiver_thread_id",
"sender_thread_id",
"status",
"type"
],
"title": "CollabResumeEndEventMsg",
"type": "object"
}
],
"title": "EventMsg"
}