2026-02-01 23:38:43 -08:00
{
"$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 auto‑ approved. Everything else will ask the user to approve." ,
"enum" : [
"untrusted"
] ,
"type" : "string"
} ,
{
2026-02-12 13:23:30 -08:00
"description" : "DEPRECATED: *All* commands are auto‑ approved, 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." ,
2026-02-01 23:38:43 -08:00
"enum" : [
"on-failure"
] ,
"type" : "string"
} ,
{
"description" : "The model decides when to ask the user for approval." ,
"enum" : [
"on-request"
] ,
"type" : "string"
} ,
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"
} ,
2026-02-01 23:38:43 -08:00
{
"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 ,
2026-02-01 23:38:43 -08:00
"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 ,
2026-02-01 23:38:43 -08:00
"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" ,
2026-02-11 17:16:27 -08:00
"server_overloaded" ,
2026-02-01 23:38:43 -08:00
"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"
}
]
} ,
2026-02-20 15:26:33 +00:00
"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"
} ,
2026-02-01 23:38:43 -08:00
"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"
} ,
2026-02-25 12:00:10 -08:00
"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" : {
2026-03-06 01:50:26 -08:00
"_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" : {
2026-03-06 01:50:26 -08:00
"_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"
}
]
} ,
2026-02-01 23:38:43 -08:00
"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"
} ,
2026-02-20 19:06:35 -08:00
{
"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"
} ,
2026-02-17 11:02:23 -08:00
{
"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"
} ,
2026-02-01 23:38:43 -08:00
{
"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"
}
] ,
2026-02-03 09:23:53 -08:00
"default" : "default"
2026-02-01 23:38:43 -08:00
} ,
"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"
} ,
2026-02-01 23:38:43 -08:00
"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" ,
2026-02-01 23:38:43 -08:00
"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"
} ,
2026-02-01 23:38:43 -08:00
"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" ,
2026-02-01 23:38:43 -08:00
"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"
} ,
2026-02-19 09:56:56 -08:00
"phase" : {
"anyOf" : [
{
"$ref" : "#/definitions/MessagePhase"
} ,
{
"type" : "null"
}
] ,
"default" : null
} ,
2026-02-01 23:38:43 -08:00
"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"
} ,
2026-02-10 02:09:23 -08:00
"network_proxy" : {
"anyOf" : [
{
"$ref" : "#/definitions/SessionNetworkProxyRuntime"
} ,
{
"type" : "null"
}
] ,
"description" : "Runtime proxy bind addresses, when the managed proxy was started for this session."
} ,
2026-02-01 23:38:43 -08:00
"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"
} ,
2026-03-03 02:35:09 -08:00
"service_tier" : {
"anyOf" : [
{
"$ref" : "#/definitions/ServiceTier"
} ,
{
"type" : "null"
}
]
} ,
2026-02-01 23:38:43 -08:00
"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" : {
2026-03-06 17:33:46 -08:00
"$ref" : "#/definitions/ResponsesApiWebSearchAction"
2026-02-01 23:38:43 -08:00
} ,
"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"
} ,
2026-03-04 16:54:38 -08:00
{
"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"
]
} ,
2026-03-06 00:47:21 -08:00
"saved_path" : {
"type" : [
"string" ,
"null"
]
} ,
2026-03-04 16:54:38 -08:00
"status" : {
"type" : "string"
} ,
"type" : {
"enum" : [
"image_generation_end"
] ,
"title" : "ImageGenerationEndEventMsgType" ,
"type" : "string"
}
} ,
"required" : [
"call_id" ,
"result" ,
"status" ,
"type"
] ,
"title" : "ImageGenerationEndEventMsg" ,
"type" : "object"
} ,
2026-02-01 23:38:43 -08:00
{
"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."
} ,
2026-02-01 23:38:43 -08:00
"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" ,
2026-02-01 23:38:43 -08:00
"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" : {
2026-02-24 09:48:57 -08:00
"additional_permissions" : {
"anyOf" : [
{
2026-02-24 19:35:28 -08:00
"$ref" : "#/definitions/PermissionProfile"
2026-02-24 09:48:57 -08:00
} ,
{
"type" : "null"
}
] ,
"description" : "Optional additional filesystem permissions requested for this command."
} ,
2026-02-17 17:55:57 -08:00
"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"
]
} ,
2026-02-01 23:38:43 -08:00
"call_id" : {
2026-02-17 17:55:57 -08:00
"description" : "Identifier for the associated command execution item." ,
2026-02-01 23:38:43 -08:00
"type" : "string"
} ,
"command" : {
"description" : "The command to be executed." ,
"items" : {
"type" : "string"
} ,
"type" : "array"
} ,
"cwd" : {
"description" : "The command's working directory." ,
"type" : "string"
} ,
2026-02-13 20:18:12 -08:00
"network_approval_context" : {
"anyOf" : [
{
"$ref" : "#/definitions/NetworkApprovalContext"
} ,
{
"type" : "null"
}
] ,
"description" : "Optional network context for a blocked request that can be approved."
} ,
2026-02-01 23:38:43 -08:00
"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"
]
} ,
2026-02-23 21:37:46 -08:00
"proposed_network_policy_amendments" : {
"description" : "Proposed network policy amendments (for example allow/deny this host in future)." ,
"items" : {
"$ref" : "#/definitions/NetworkPolicyAmendment"
} ,
"type" : [
"array" ,
"null"
]
} ,
2026-02-01 23:38:43 -08:00
"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."
} ,
2026-02-01 23:38:43 -08:00
"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"
} ,
2026-03-08 20:23:06 -07:00
{
"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"
} ,
2026-02-01 23:38:43 -08:00
{
"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"
} ,
2026-02-25 12:00:10 -08:00
{
"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"
} ,
2026-02-01 23:38:43 -08:00
{
"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"
2026-02-01 23:38:43 -08:00
} ,
"server_name" : {
"type" : "string"
} ,
2026-03-06 01:50:26 -08:00
"turn_id" : {
"description" : "Turn ID that this elicitation belongs to, when known." ,
"type" : [
"string" ,
"null"
]
} ,
2026-02-01 23:38:43 -08:00
"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" ,
2026-02-01 23:38:43 -08:00
"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 front‑ ends 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."
} ,
2026-02-01 23:38:43 -08:00
"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" ,
2026-02-01 23:38:43 -08:00
"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"
} ,
2026-02-03 14:09:37 -08:00
{
"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"
} ,
2026-02-01 23:38:43 -08:00
{
"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"
]
} ,
2026-02-01 23:38:43 -08:00
"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"
} ,
2026-03-09 21:11:31 -07:00
{
"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"
} ,
2026-02-01 23:38:43 -08:00
{
"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"
} ,
2026-03-10 17:46:25 -07:00
"model" : {
"type" : "string"
} ,
2026-02-01 23:38:43 -08:00
"prompt" : {
"description" : "Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the beginning." ,
"type" : "string"
} ,
2026-03-10 17:46:25 -07:00
"reasoning_effort" : {
"$ref" : "#/definitions/ReasoningEffort"
} ,
2026-02-01 23:38:43 -08:00
"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" ,
2026-03-10 17:46:25 -07:00
"model" ,
2026-02-01 23:38:43 -08:00
"prompt" ,
2026-03-10 17:46:25 -07:00
"reasoning_effort" ,
2026-02-01 23:38:43 -08:00
"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"
} ,
2026-02-20 15:26:33 +00:00
"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"
]
} ,
2026-02-01 23:38:43 -08:00
"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"
} ,
2026-02-20 15:26:33 +00:00
"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"
]
} ,
2026-02-01 23:38:43 -08:00
"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"
} ,
2026-02-20 15:26:33 +00:00
"receiver_agents" : {
"description" : "Optional nicknames/roles for receivers." ,
"items" : {
"$ref" : "#/definitions/CollabAgentRef"
} ,
"type" : "array"
} ,
2026-02-01 23:38:43 -08:00
"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" : {
2026-02-20 15:26:33 +00:00
"agent_statuses" : {
"description" : "Optional receiver metadata paired with final statuses." ,
"items" : {
"$ref" : "#/definitions/CollabAgentStatusEntry"
} ,
"type" : "array"
} ,
2026-02-01 23:38:43 -08:00
"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"
} ,
2026-02-20 15:26:33 +00:00
"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"
]
} ,
2026-02-01 23:38:43 -08:00
"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"
2026-02-07 17:31:45 +01:00
} ,
{
"description" : "Collab interaction: resume begin." ,
"properties" : {
"call_id" : {
"description" : "Identifier for the collab tool call." ,
"type" : "string"
} ,
2026-02-20 15:26:33 +00:00
"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"
]
} ,
2026-02-07 17:31:45 +01:00
"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"
} ,
2026-02-20 15:26:33 +00:00
"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"
]
} ,
2026-02-07 17:31:45 +01:00
"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"
2026-02-01 23:38:43 -08:00
}
]
} ,
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"
} ,
2026-02-01 23:38:43 -08:00
"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"
} ,
2026-02-01 23:38:43 -08:00
"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"
}
]
} ,
2026-02-24 19:35:28 -08:00
"FileSystemPermissions" : {
"properties" : {
"read" : {
"items" : {
2026-02-27 09:42:52 -08:00
"$ref" : "#/definitions/AbsolutePathBuf"
2026-02-24 19:35:28 -08:00
} ,
"type" : [
"array" ,
"null"
]
} ,
"write" : {
"items" : {
2026-02-27 09:42:52 -08:00
"$ref" : "#/definitions/AbsolutePathBuf"
2026-02-24 19:35:28 -08:00
} ,
"type" : [
"array" ,
"null"
]
}
} ,
"type" : "object"
} ,
feat(app-server, core): allow text + image content items for dynamic tool outputs (#10567)
Took over the work that @aaronl-openai started here:
https://github.com/openai/codex/pull/10397
Now that app-server clients are able to set up custom tools (called
`dynamic_tools` in app-server), we should expose a way for clients to
pass in not just text, but also image outputs. This is something the
Responses API already supports for function call outputs, where you can
pass in either a string or an array of content outputs (text, image,
file):
https://platform.openai.com/docs/api-reference/responses/create#responses_create-input-input_item_list-item-function_tool_call_output-output-array-input_image
So let's just plumb it through in Codex (with the caveat that we only
support text and image for now). This is implemented end-to-end across
app-server v2 protocol types and core tool handling.
## Breaking API change
NOTE: This introduces a breaking change with dynamic tools, but I think
it's ok since this concept was only recently introduced
(https://github.com/openai/codex/pull/9539) and it's better to get the
API contract correct. I don't think there are any real consumers of this
yet (not even the Codex App).
Old shape:
`{ "output": "dynamic-ok", "success": true }`
New shape:
```
{
"contentItems": [
{ "type": "inputText", "text": "dynamic-ok" },
{ "type": "inputImage", "imageUrl": "data:image/png;base64,AAA" }
]
"success": true
}
```
2026-02-04 16:12:47 -08:00
"FunctionCallOutputBody" : {
"anyOf" : [
{
"type" : "string"
} ,
{
"items" : {
"$ref" : "#/definitions/FunctionCallOutputContentItem"
} ,
"type" : "array"
}
]
} ,
2026-02-01 23:38:43 -08:00
"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" : {
2026-03-03 15:56:54 -08:00
"detail" : {
"anyOf" : [
{
"$ref" : "#/definitions/ImageDetail"
} ,
{
"type" : "null"
}
]
} ,
2026-02-01 23:38:43 -08:00
"image_url" : {
"type" : "string"
} ,
"type" : {
"enum" : [
"input_image"
] ,
"title" : "InputImageFunctionCallOutputContentItemType" ,
"type" : "string"
}
} ,
"required" : [
"image_url" ,
"type"
] ,
"title" : "InputImageFunctionCallOutputContentItem" ,
"type" : "object"
}
]
} ,
"FunctionCallOutputPayload" : {
feat(app-server, core): allow text + image content items for dynamic tool outputs (#10567)
Took over the work that @aaronl-openai started here:
https://github.com/openai/codex/pull/10397
Now that app-server clients are able to set up custom tools (called
`dynamic_tools` in app-server), we should expose a way for clients to
pass in not just text, but also image outputs. This is something the
Responses API already supports for function call outputs, where you can
pass in either a string or an array of content outputs (text, image,
file):
https://platform.openai.com/docs/api-reference/responses/create#responses_create-input-input_item_list-item-function_tool_call_output-output-array-input_image
So let's just plumb it through in Codex (with the caveat that we only
support text and image for now). This is implemented end-to-end across
app-server v2 protocol types and core tool handling.
## Breaking API change
NOTE: This introduces a breaking change with dynamic tools, but I think
it's ok since this concept was only recently introduced
(https://github.com/openai/codex/pull/9539) and it's better to get the
API contract correct. I don't think there are any real consumers of this
yet (not even the Codex App).
Old shape:
`{ "output": "dynamic-ok", "success": true }`
New shape:
```
{
"contentItems": [
{ "type": "inputText", "text": "dynamic-ok" },
{ "type": "inputImage", "imageUrl": "data:image/png;base64,AAA" }
]
"success": true
}
```
2026-02-04 16:12:47 -08:00
"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." ,
2026-02-01 23:38:43 -08:00
"properties" : {
feat(app-server, core): allow text + image content items for dynamic tool outputs (#10567)
Took over the work that @aaronl-openai started here:
https://github.com/openai/codex/pull/10397
Now that app-server clients are able to set up custom tools (called
`dynamic_tools` in app-server), we should expose a way for clients to
pass in not just text, but also image outputs. This is something the
Responses API already supports for function call outputs, where you can
pass in either a string or an array of content outputs (text, image,
file):
https://platform.openai.com/docs/api-reference/responses/create#responses_create-input-input_item_list-item-function_tool_call_output-output-array-input_image
So let's just plumb it through in Codex (with the caveat that we only
support text and image for now). This is implemented end-to-end across
app-server v2 protocol types and core tool handling.
## Breaking API change
NOTE: This introduces a breaking change with dynamic tools, but I think
it's ok since this concept was only recently introduced
(https://github.com/openai/codex/pull/9539) and it's better to get the
API contract correct. I don't think there are any real consumers of this
yet (not even the Codex App).
Old shape:
`{ "output": "dynamic-ok", "success": true }`
New shape:
```
{
"contentItems": [
{ "type": "inputText", "text": "dynamic-ok" },
{ "type": "inputImage", "imageUrl": "data:image/png;base64,AAA" }
]
"success": true
}
```
2026-02-04 16:12:47 -08:00
"body" : {
"$ref" : "#/definitions/FunctionCallOutputBody"
2026-02-01 23:38:43 -08:00
} ,
"success" : {
"type" : [
"boolean" ,
"null"
]
}
} ,
"required" : [
feat(app-server, core): allow text + image content items for dynamic tool outputs (#10567)
Took over the work that @aaronl-openai started here:
https://github.com/openai/codex/pull/10397
Now that app-server clients are able to set up custom tools (called
`dynamic_tools` in app-server), we should expose a way for clients to
pass in not just text, but also image outputs. This is something the
Responses API already supports for function call outputs, where you can
pass in either a string or an array of content outputs (text, image,
file):
https://platform.openai.com/docs/api-reference/responses/create#responses_create-input-input_item_list-item-function_tool_call_output-output-array-input_image
So let's just plumb it through in Codex (with the caveat that we only
support text and image for now). This is implemented end-to-end across
app-server v2 protocol types and core tool handling.
## Breaking API change
NOTE: This introduces a breaking change with dynamic tools, but I think
it's ok since this concept was only recently introduced
(https://github.com/openai/codex/pull/9539) and it's better to get the
API contract correct. I don't think there are any real consumers of this
yet (not even the Codex App).
Old shape:
`{ "output": "dynamic-ok", "success": true }`
New shape:
```
{
"contentItems": [
{ "type": "inputText", "text": "dynamic-ok" },
{ "type": "inputImage", "imageUrl": "data:image/png;base64,AAA" }
]
"success": true
}
```
2026-02-04 16:12:47 -08:00
"body"
2026-02-01 23:38:43 -08:00
] ,
"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"
} ,
2026-03-09 21:11:31 -07:00
"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"
} ,
2026-03-03 15:56:54 -08:00
"ImageDetail" : {
"enum" : [
"auto" ,
"low" ,
"high" ,
"original"
] ,
"type" : "string"
} ,
2026-02-01 23:38:43 -08:00
"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"
} ,
2026-03-05 16:21:45 -08:00
"MacOsAutomationPermission" : {
"oneOf" : [
2026-02-24 19:35:28 -08:00
{
2026-03-05 16:21:45 -08:00
"enum" : [
"none" ,
"all"
] ,
"type" : "string"
2026-02-24 19:35:28 -08:00
} ,
{
2026-03-05 16:21:45 -08:00
"additionalProperties" : false ,
"properties" : {
"bundle_ids" : {
"items" : {
"type" : "string"
} ,
"type" : "array"
}
2026-02-24 19:35:28 -08:00
} ,
2026-03-05 16:21:45 -08:00
"required" : [
"bundle_ids"
] ,
"title" : "BundleIdsMacOsAutomationPermission" ,
"type" : "object"
2026-02-24 19:35:28 -08:00
}
]
} ,
2026-03-10 16:34:47 -07:00
"MacOsContactsPermission" : {
"enum" : [
"none" ,
"read_only" ,
"read_write"
] ,
"type" : "string"
} ,
2026-03-05 16:21:45 -08:00
"MacOsPreferencesPermission" : {
"enum" : [
"none" ,
"read_only" ,
"read_write"
] ,
"type" : "string"
} ,
"MacOsSeatbeltProfileExtensions" : {
2026-02-24 19:35:28 -08:00
"properties" : {
2026-03-05 16:21:45 -08:00
"macos_accessibility" : {
2026-03-05 22:02:33 -08:00
"default" : false ,
2026-03-05 16:21:45 -08:00
"type" : "boolean"
2026-02-24 19:35:28 -08:00
} ,
2026-03-05 16:21:45 -08:00
"macos_automation" : {
2026-03-05 22:02:33 -08:00
"allOf" : [
{
"$ref" : "#/definitions/MacOsAutomationPermission"
}
] ,
"default" : "none"
2026-02-24 19:35:28 -08:00
} ,
2026-03-05 16:21:45 -08:00
"macos_calendar" : {
2026-03-05 22:02:33 -08:00
"default" : false ,
2026-03-05 16:21:45 -08:00
"type" : "boolean"
2026-02-24 19:35:28 -08:00
} ,
2026-03-10 16:34:47 -07:00
"macos_contacts" : {
"allOf" : [
{
"$ref" : "#/definitions/MacOsContactsPermission"
}
] ,
"default" : "none"
} ,
"macos_launch_services" : {
"default" : false ,
"type" : "boolean"
} ,
2026-03-05 16:21:45 -08:00
"macos_preferences" : {
2026-03-05 22:02:33 -08:00
"allOf" : [
{
"$ref" : "#/definitions/MacOsPreferencesPermission"
}
] ,
"default" : "read_only"
2026-03-10 16:34:47 -07:00
} ,
"macos_reminders" : {
"default" : false ,
"type" : "boolean"
2026-02-24 19:35:28 -08:00
}
} ,
"type" : "object"
} ,
2026-02-01 23:38:43 -08:00
"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"
] ,
2026-02-20 21:36:12 -08:00
"title" : "StartingMcpStartupStatus" ,
2026-02-01 23:38:43 -08:00
"type" : "object"
} ,
{
"properties" : {
"state" : {
"enum" : [
"ready"
] ,
"type" : "string"
}
} ,
"required" : [
"state"
] ,
2026-02-20 21:36:12 -08:00
"title" : "ReadyMcpStartupStatus" ,
2026-02-01 23:38:43 -08:00
"type" : "object"
} ,
{
"properties" : {
"error" : {
"type" : "string"
} ,
"state" : {
"enum" : [
"failed"
] ,
"type" : "string"
}
} ,
"required" : [
"error" ,
"state"
] ,
"type" : "object"
} ,
{
"properties" : {
"state" : {
"enum" : [
"cancelled"
] ,
"type" : "string"
}
} ,
"required" : [
"state"
] ,
2026-02-20 21:36:12 -08:00
"title" : "CancelledMcpStartupStatus" ,
2026-02-01 23:38:43 -08:00
"type" : "object"
}
]
} ,
2026-02-02 18:52:26 -08:00
"MessagePhase" : {
2026-02-06 18:39:52 -08:00
"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"
}
]
2026-02-02 18:52:26 -08:00
} ,
2026-02-01 23:38:43 -08:00
"ModeKind" : {
"description" : "Initial collaboration mode to use when the TUI starts." ,
"enum" : [
"plan" ,
2026-02-03 09:23:53 -08:00
"default"
2026-02-01 23:38:43 -08:00
] ,
"type" : "string"
} ,
2026-02-17 11:02:23 -08:00
"ModelRerouteReason" : {
"enum" : [
"high_risk_cyber_activity"
] ,
"type" : "string"
} ,
2026-02-01 23:38:43 -08:00
"NetworkAccess" : {
"description" : "Represents whether outbound network access is available to the agent." ,
"enum" : [
"restricted" ,
"enabled"
] ,
"type" : "string"
} ,
2026-02-13 20:18:12 -08:00
"NetworkApprovalContext" : {
"properties" : {
"host" : {
"type" : "string"
} ,
"protocol" : {
"$ref" : "#/definitions/NetworkApprovalProtocol"
}
} ,
"required" : [
"host" ,
"protocol"
] ,
"type" : "object"
} ,
"NetworkApprovalProtocol" : {
"enum" : [
"http" ,
"https" ,
"socks5_tcp" ,
"socks5_udp"
] ,
"type" : "string"
} ,
2026-03-03 20:57:29 -08:00
"NetworkPermissions" : {
"properties" : {
"enabled" : {
"type" : [
"boolean" ,
"null"
]
}
} ,
"type" : "object"
} ,
2026-02-23 21:37:46 -08:00
"NetworkPolicyAmendment" : {
"properties" : {
"action" : {
"$ref" : "#/definitions/NetworkPolicyRuleAction"
} ,
"host" : {
"type" : "string"
}
} ,
"required" : [
"action" ,
"host"
] ,
"type" : "object"
} ,
"NetworkPolicyRuleAction" : {
"enum" : [
"allow" ,
"deny"
] ,
"type" : "string"
} ,
2026-02-01 23:38:43 -08:00
"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"
} ,
2026-02-24 19:35:28 -08:00
"PermissionProfile" : {
"properties" : {
"file_system" : {
"anyOf" : [
{
"$ref" : "#/definitions/FileSystemPermissions"
} ,
{
"type" : "null"
}
]
} ,
"macos" : {
"anyOf" : [
{
2026-03-05 16:21:45 -08:00
"$ref" : "#/definitions/MacOsSeatbeltProfileExtensions"
2026-02-24 19:35:28 -08:00
} ,
{
"type" : "null"
}
]
} ,
"network" : {
2026-03-03 20:57:29 -08:00
"anyOf" : [
{
"$ref" : "#/definitions/NetworkPermissions"
} ,
{
"type" : "null"
}
2026-02-24 19:35:28 -08:00
]
}
} ,
"type" : "object"
} ,
2026-02-01 23:38:43 -08:00
"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"
}
]
} ,
2026-02-10 20:09:31 -08:00
"limit_id" : {
"type" : [
"string" ,
"null"
]
} ,
"limit_name" : {
"type" : [
"string" ,
"null"
]
} ,
2026-02-01 23:38:43 -08:00
"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"
} ,
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"
}
]
} ,
2026-02-20 19:06:35 -08:00
"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" : {
2026-03-02 16:05:40 -08:00
"instructions" : {
2026-02-20 19:06:35 -08:00
"type" : [
"string" ,
"null"
]
2026-03-02 16:05:40 -08:00
} ,
"session_id" : {
"type" : "string"
2026-02-20 19:06:35 -08:00
}
} ,
2026-03-02 16:05:40 -08:00
"required" : [
"session_id"
] ,
2026-02-20 19:06:35 -08:00
"type" : "object"
}
} ,
"required" : [
"SessionUpdated"
] ,
"title" : "SessionUpdatedRealtimeEvent" ,
"type" : "object"
} ,
2026-03-09 22:30:03 -07:00
{
"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"
} ,
2026-02-20 19:06:35 -08:00
{
"additionalProperties" : false ,
"properties" : {
"AudioOut" : {
"$ref" : "#/definitions/RealtimeAudioFrame"
}
} ,
"required" : [
"AudioOut"
] ,
"title" : "AudioOutRealtimeEvent" ,
"type" : "object"
} ,
{
"additionalProperties" : false ,
"properties" : {
"ConversationItemAdded" : true
} ,
"required" : [
"ConversationItemAdded"
] ,
"title" : "ConversationItemAddedRealtimeEvent" ,
"type" : "object"
} ,
2026-03-02 16:05:40 -08:00
{
"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"
} ,
2026-02-20 19:06:35 -08:00
{
"additionalProperties" : false ,
"properties" : {
"Error" : {
"type" : "string"
}
} ,
"required" : [
"Error"
] ,
"title" : "ErrorRealtimeEvent" ,
"type" : "object"
}
]
} ,
2026-03-09 22:30:03 -07:00
"RealtimeHandoffRequested" : {
2026-03-02 16:05:40 -08:00
"properties" : {
2026-03-09 22:30:03 -07:00
"active_transcript" : {
"items" : {
"$ref" : "#/definitions/RealtimeTranscriptEntry"
} ,
"type" : "array"
} ,
"handoff_id" : {
2026-03-02 16:05:40 -08:00
"type" : "string"
} ,
2026-03-09 22:30:03 -07:00
"input_transcript" : {
"type" : "string"
} ,
"item_id" : {
2026-03-02 16:05:40 -08:00
"type" : "string"
}
} ,
"required" : [
2026-03-09 22:30:03 -07:00
"active_transcript" ,
"handoff_id" ,
"input_transcript" ,
"item_id"
2026-03-02 16:05:40 -08:00
] ,
"type" : "object"
} ,
2026-03-09 22:30:03 -07:00
"RealtimeTranscriptDelta" : {
2026-03-02 16:05:40 -08:00
"properties" : {
2026-03-09 22:30:03 -07:00
"delta" : {
2026-03-02 16:05:40 -08:00
"type" : "string"
2026-03-09 22:30:03 -07:00
}
} ,
"required" : [
"delta"
] ,
"type" : "object"
} ,
"RealtimeTranscriptEntry" : {
"properties" : {
"role" : {
2026-03-02 16:05:40 -08:00
"type" : "string"
} ,
2026-03-09 22:30:03 -07:00
"text" : {
2026-03-02 16:05:40 -08:00
"type" : "string"
}
} ,
"required" : [
2026-03-09 22:30:03 -07:00
"role" ,
"text"
2026-03-02 16:05:40 -08:00
] ,
"type" : "object"
} ,
2026-02-01 23:38:43 -08:00
"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"
}
]
} ,
2026-02-19 11:41:49 -08:00
"RejectConfig" : {
"properties" : {
"mcp_elicitations" : {
"description" : "Reject MCP elicitation prompts." ,
"type" : "boolean"
} ,
2026-03-09 18:16:54 -07:00
"request_permissions" : {
2026-03-09 21:56:23 -07:00
"default" : false ,
2026-03-09 18:16:54 -07:00
"description" : "Reject approval prompts related to built-in permission requests." ,
"type" : "boolean"
} ,
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"
2026-03-10 16:58:23 -07:00
} ,
"skill_approval" : {
"default" : false ,
"description" : "Reject approval prompts triggered by skill script execution." ,
"type" : "boolean"
2026-02-19 11:41:49 -08:00
}
} ,
"required" : [
"mcp_elicitations" ,
"rules" ,
"sandbox_approval"
] ,
"type" : "object"
} ,
2026-02-03 14:09:37 -08:00
"RemoteSkillSummary" : {
"properties" : {
"description" : {
"type" : "string"
} ,
"id" : {
"type" : "string"
} ,
"name" : {
"type" : "string"
}
} ,
"required" : [
"description" ,
"id" ,
"name"
] ,
"type" : "object"
} ,
2026-02-01 23:38:43 -08:00
"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."
2026-02-01 23:38:43 -08:00
} ,
"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 ,
2026-02-01 23:38:43 -08:00
"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 ,
2026-02-01 23:38:43 -08:00
"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" ,
2026-02-01 23:38:43 -08:00
"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 ,
2026-02-01 23:38:43 -08:00
"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
} ,
2026-02-02 18:52:26 -08:00
"phase" : {
"anyOf" : [
{
"$ref" : "#/definitions/MessagePhase"
} ,
{
"type" : "null"
}
]
} ,
2026-02-01 23:38:43 -08:00
"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" : {
2026-02-03 11:31:57 +00:00
"description" : "Legacy id field retained for compatibility with older payloads." ,
2026-02-01 23:38:43 -08:00
"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" : {
2026-02-26 18:17:46 -08:00
"$ref" : "#/definitions/FunctionCallOutputPayload"
2026-02-01 23:38:43 -08:00
} ,
"type" : {
"enum" : [
"custom_tool_call_output"
] ,
"title" : "CustomToolCallOutputResponseItemType" ,
"type" : "string"
}
} ,
"required" : [
"call_id" ,
"output" ,
"type"
] ,
"title" : "CustomToolCallOutputResponseItem" ,
"type" : "object"
} ,
{
"properties" : {
"action" : {
"anyOf" : [
{
2026-03-06 17:33:46 -08:00
"$ref" : "#/definitions/ResponsesApiWebSearchAction"
2026-02-01 23:38:43 -08:00
} ,
{
"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"
} ,
2026-03-03 23:11:28 -08:00
{
"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"
} ,
2026-02-01 23:38:43 -08:00
{
"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"
}
]
} ,
2026-03-06 17:33:46 -08:00
"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"
}
]
} ,
2026-02-01 23:38:43 -08:00
"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"
}
]
} ,
2026-02-01 23:38:43 -08:00
"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"
} ,
{
2026-02-11 18:31:14 -08:00
"description" : "Read-only access configuration." ,
2026-02-01 23:38:43 -08:00
"properties" : {
2026-02-11 18:31:14 -08:00
"access" : {
"allOf" : [
{
"$ref" : "#/definitions/ReadOnlyAccess"
}
] ,
"description" : "Read access granted while running under this policy."
} ,
2026-03-03 18:41:57 -08:00
"network_access" : {
"description" : "When set to `true`, outbound network access is allowed. `false` by default." ,
"type" : "boolean"
} ,
2026-02-01 23:38:43 -08:00
"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"
} ,
2026-02-11 18:31:14 -08:00
"read_only_access" : {
"allOf" : [
{
"$ref" : "#/definitions/ReadOnlyAccess"
}
] ,
"description" : "Read access granted while running under this policy."
} ,
2026-02-01 23:38:43 -08:00
"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"
}
]
} ,
2026-03-03 02:35:09 -08:00
"ServiceTier" : {
"enum" : [
2026-03-03 22:46:05 -08:00
"fast" ,
"flex"
2026-03-03 02:35:09 -08:00
] ,
"type" : "string"
} ,
2026-02-10 02:09:23 -08:00
"SessionNetworkProxyRuntime" : {
"properties" : {
"http_addr" : {
"type" : "string"
} ,
"socks_addr" : {
"type" : "string"
}
} ,
"required" : [
"http_addr" ,
"socks_addr"
] ,
"type" : "object"
} ,
2026-02-01 23:38:43 -08:00
"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 ,
2026-02-01 23:38:43 -08:00
"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"
]
2026-02-01 23:38:43 -08:00
} ,
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 ,
2026-02-01 23:38:43 -08:00
"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 ,
2026-02-01 23:38:43 -08:00
"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"
} ,
{
2026-02-06 18:39:52 -08:00
"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`." ,
2026-02-01 23:38:43 -08:00
"properties" : {
"content" : {
"items" : {
"$ref" : "#/definitions/AgentMessageContent"
} ,
"type" : "array"
} ,
"id" : {
"type" : "string"
} ,
2026-02-06 18:39:52 -08:00
"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."
} ,
2026-02-01 23:38:43 -08:00
"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" : {
2026-03-06 17:33:46 -08:00
"$ref" : "#/definitions/ResponsesApiWebSearchAction"
2026-02-01 23:38:43 -08:00
} ,
"id" : {
"type" : "string"
} ,
"query" : {
"type" : "string"
} ,
"type" : {
"enum" : [
"WebSearch"
] ,
"title" : "WebSearchTurnItemType" ,
"type" : "string"
}
} ,
"required" : [
"action" ,
"id" ,
"query" ,
"type"
] ,
"title" : "WebSearchTurnItem" ,
"type" : "object"
} ,
2026-03-04 16:54:38 -08:00
{
"properties" : {
"id" : {
"type" : "string"
} ,
"result" : {
"type" : "string"
} ,
"revised_prompt" : {
"type" : [
"string" ,
"null"
]
} ,
2026-03-06 00:47:21 -08:00
"saved_path" : {
"type" : [
"string" ,
"null"
]
} ,
2026-03-04 16:54:38 -08:00
"status" : {
"type" : "string"
} ,
"type" : {
"enum" : [
"ImageGeneration"
] ,
"title" : "ImageGenerationTurnItemType" ,
"type" : "string"
}
} ,
"required" : [
"id" ,
"result" ,
"status" ,
"type"
] ,
"title" : "ImageGenerationTurnItem" ,
"type" : "object"
} ,
2026-02-01 23:38:43 -08:00
{
"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" : "Pre‑ encoded 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"
} ,
{
2026-03-06 11:08:36 -08:00
"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>`." ,
2026-02-01 23:38:43 -08:00
"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"
} ,
2026-02-20 19:06:35 -08:00
{
"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"
} ,
2026-02-17 11:02:23 -08:00
{
"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"
} ,
2026-02-01 23:38:43 -08:00
{
"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"
}
] ,
2026-02-03 09:23:53 -08:00
"default" : "default"
2026-02-01 23:38:43 -08:00
} ,
"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"
} ,
2026-02-01 23:38:43 -08:00
"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" ,
2026-02-01 23:38:43 -08:00
"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"
} ,
2026-02-01 23:38:43 -08:00
"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" ,
2026-02-01 23:38:43 -08:00
"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"
} ,
2026-02-19 09:56:56 -08:00
"phase" : {
"anyOf" : [
{
"$ref" : "#/definitions/MessagePhase"
} ,
{
"type" : "null"
}
] ,
"default" : null
} ,
2026-02-01 23:38:43 -08:00
"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"
} ,
2026-02-10 02:09:23 -08:00
"network_proxy" : {
"anyOf" : [
{
"$ref" : "#/definitions/SessionNetworkProxyRuntime"
} ,
{
"type" : "null"
}
] ,
"description" : "Runtime proxy bind addresses, when the managed proxy was started for this session."
} ,
2026-02-01 23:38:43 -08:00
"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"
} ,
2026-03-03 02:35:09 -08:00
"service_tier" : {
"anyOf" : [
{
"$ref" : "#/definitions/ServiceTier"
} ,
{
"type" : "null"
}
]
} ,
2026-02-01 23:38:43 -08:00
"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" : {
2026-03-06 17:33:46 -08:00
"$ref" : "#/definitions/ResponsesApiWebSearchAction"
2026-02-01 23:38:43 -08:00
} ,
"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"
} ,
2026-03-04 16:54:38 -08:00
{
"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"
]
} ,
2026-03-06 00:47:21 -08:00
"saved_path" : {
"type" : [
"string" ,
"null"
]
} ,
2026-03-04 16:54:38 -08:00
"status" : {
"type" : "string"
} ,
"type" : {
"enum" : [
"image_generation_end"
] ,
"title" : "ImageGenerationEndEventMsgType" ,
"type" : "string"
}
} ,
"required" : [
"call_id" ,
"result" ,
"status" ,
"type"
] ,
"title" : "ImageGenerationEndEventMsg" ,
"type" : "object"
} ,
2026-02-01 23:38:43 -08:00
{
"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."
} ,
2026-02-01 23:38:43 -08:00
"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" ,
2026-02-01 23:38:43 -08:00
"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" : {
2026-02-24 09:48:57 -08:00
"additional_permissions" : {
"anyOf" : [
{
2026-02-24 19:35:28 -08:00
"$ref" : "#/definitions/PermissionProfile"
2026-02-24 09:48:57 -08:00
} ,
{
"type" : "null"
}
] ,
"description" : "Optional additional filesystem permissions requested for this command."
} ,
2026-02-17 17:55:57 -08:00
"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"
]
} ,
2026-02-01 23:38:43 -08:00
"call_id" : {
2026-02-17 17:55:57 -08:00
"description" : "Identifier for the associated command execution item." ,
2026-02-01 23:38:43 -08:00
"type" : "string"
} ,
"command" : {
"description" : "The command to be executed." ,
"items" : {
"type" : "string"
} ,
"type" : "array"
} ,
"cwd" : {
"description" : "The command's working directory." ,
"type" : "string"
} ,
2026-02-13 20:18:12 -08:00
"network_approval_context" : {
"anyOf" : [
{
"$ref" : "#/definitions/NetworkApprovalContext"
} ,
{
"type" : "null"
}
] ,
"description" : "Optional network context for a blocked request that can be approved."
} ,
2026-02-01 23:38:43 -08:00
"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"
]
} ,
2026-02-23 21:37:46 -08:00
"proposed_network_policy_amendments" : {
"description" : "Proposed network policy amendments (for example allow/deny this host in future)." ,
"items" : {
"$ref" : "#/definitions/NetworkPolicyAmendment"
} ,
"type" : [
"array" ,
"null"
]
} ,
2026-02-01 23:38:43 -08:00
"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."
} ,
2026-02-01 23:38:43 -08:00
"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"
} ,
2026-03-08 20:23:06 -07:00
{
"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"
} ,
2026-02-01 23:38:43 -08:00
{
"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"
} ,
2026-02-25 12:00:10 -08:00
{
"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"
} ,
2026-02-01 23:38:43 -08:00
{
"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"
2026-02-01 23:38:43 -08:00
} ,
"server_name" : {
"type" : "string"
} ,
2026-03-06 01:50:26 -08:00
"turn_id" : {
"description" : "Turn ID that this elicitation belongs to, when known." ,
"type" : [
"string" ,
"null"
]
} ,
2026-02-01 23:38:43 -08:00
"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" ,
2026-02-01 23:38:43 -08:00
"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 front‑ ends 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."
} ,
2026-02-01 23:38:43 -08:00
"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" ,
2026-02-01 23:38:43 -08:00
"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"
} ,
2026-02-03 14:09:37 -08:00
{
"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"
} ,
2026-02-01 23:38:43 -08:00
{
"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"
]
} ,
2026-02-01 23:38:43 -08:00
"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"
} ,
2026-03-09 21:11:31 -07:00
{
"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"
} ,
2026-02-01 23:38:43 -08:00
{
"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"
} ,
2026-03-10 17:46:25 -07:00
"model" : {
"type" : "string"
} ,
2026-02-01 23:38:43 -08:00
"prompt" : {
"description" : "Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the beginning." ,
"type" : "string"
} ,
2026-03-10 17:46:25 -07:00
"reasoning_effort" : {
"$ref" : "#/definitions/ReasoningEffort"
} ,
2026-02-01 23:38:43 -08:00
"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" ,
2026-03-10 17:46:25 -07:00
"model" ,
2026-02-01 23:38:43 -08:00
"prompt" ,
2026-03-10 17:46:25 -07:00
"reasoning_effort" ,
2026-02-01 23:38:43 -08:00
"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"
} ,
2026-02-20 15:26:33 +00:00
"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"
]
} ,
2026-02-01 23:38:43 -08:00
"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"
} ,
2026-02-20 15:26:33 +00:00
"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"
]
} ,
2026-02-01 23:38:43 -08:00
"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"
} ,
2026-02-20 15:26:33 +00:00
"receiver_agents" : {
"description" : "Optional nicknames/roles for receivers." ,
"items" : {
"$ref" : "#/definitions/CollabAgentRef"
} ,
"type" : "array"
} ,
2026-02-01 23:38:43 -08:00
"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" : {
2026-02-20 15:26:33 +00:00
"agent_statuses" : {
"description" : "Optional receiver metadata paired with final statuses." ,
"items" : {
"$ref" : "#/definitions/CollabAgentStatusEntry"
} ,
"type" : "array"
} ,
2026-02-01 23:38:43 -08:00
"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"
} ,
2026-02-20 15:26:33 +00:00
"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"
]
} ,
2026-02-01 23:38:43 -08:00
"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"
2026-02-07 17:31:45 +01:00
} ,
{
"description" : "Collab interaction: resume begin." ,
"properties" : {
"call_id" : {
"description" : "Identifier for the collab tool call." ,
"type" : "string"
} ,
2026-02-20 15:26:33 +00:00
"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"
]
} ,
2026-02-07 17:31:45 +01:00
"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"
} ,
2026-02-20 15:26:33 +00:00
"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"
]
} ,
2026-02-07 17:31:45 +01:00
"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"
2026-02-01 23:38:43 -08:00
}
] ,
"title" : "EventMsg"
2026-02-10 02:09:23 -08:00
}