core-agent-ide/codex-rs/app-server
2025-11-18 16:34:13 +00:00
..
src nit: app server (#6830) 2025-11-18 16:34:13 +00:00
tests Update defaults to gpt-5.1 (#6652) 2025-11-17 17:40:11 -08:00
Cargo.toml [App server] add mcp tool call item started/completed events (#6642) 2025-11-14 08:08:43 -08:00
README.md Update defaults to gpt-5.1 (#6652) 2025-11-17 17:40:11 -08:00

codex-app-server

codex app-server is the interface Codex uses to power rich interfaces such as the Codex VS Code extension. The message schema is currently unstable, but those who wish to build experimental UIs on top of Codex may find it valuable.

Table of Contents

Protocol

Similar to MCP, codex app-server supports bidirectional communication, streaming JSONL over stdio. The protocol is JSON-RPC 2.0, though the "jsonrpc":"2.0" header is omitted.

Message Schema

Currently, you can dump a TypeScript version of the schema using codex app-server generate-ts, or a JSON Schema bundle via codex app-server generate-json-schema. Each output is specific to the version of Codex you used to run the command, so the generated artifacts are guaranteed to match that version.

codex app-server generate-ts --out DIR
codex app-server generate-json-schema --out DIR

Lifecycle Overview

  • Initialize once: Immediately after launching the codex app-server process, send an initialize request with your client metadata, then emit an initialized notification. Any other request before this handshake gets rejected.
  • Start (or resume) a thread: Call thread/start to open a fresh conversation. The response returns the thread object and youll also get a thread/started notification. If youre continuing an existing conversation, call thread/resume with its ID instead.
  • Begin a turn: To send user input, call turn/start with the target threadId and the user's input. Optional fields let you override model, cwd, sandbox policy, etc. This immediately returns the new turn object and triggers a turn/started notification.
  • Stream events: After turn/start, keep reading JSON-RPC notifications on stdout. Youll see item/started, item/completed, deltas like item/agentMessage/delta, tool progress, etc. These represent streaming model output plus any side effects (commands, tool calls, reasoning notes).
  • Finish the turn: When the model is done (or the turn is interrupted via making the turn/interrupt call), the server sends turn/completed with the final turn state and token usage.

Initialization

Clients must send a single initialize request before invoking any other method, then acknowledge with an initialized notification. The server returns the user agent string it will present to upstream services; subsequent requests issued before initialization receive a "Not initialized" error, and repeated initialize calls receive an "Already initialized" error.

Example:

{ "method": "initialize", "id": 0, "params": {
    "clientInfo": { "name": "codex-vscode", "title": "Codex VS Code Extension", "version": "0.1.0" }
} }
{ "id": 0, "result": { "userAgent": "codex-app-server/0.1.0 codex-vscode/0.1.0" } }
{ "method": "initialized" }

Core primitives

We have 3 top level primitives:

  • Thread - a conversation between the Codex agent and a user. Each thread contains multiple turns.
  • Turn - one turn of the conversation, typically starting with a user message and finishing with an agent message. Each turn contains multiple items.
  • Item - represents user inputs and agent outputs as part of the turn, persisted and used as the context for future conversations.

Thread & turn endpoints

The JSON-RPC API exposes dedicated methods for managing Codex conversations. Threads store long-lived conversation metadata, and turns store the per-message exchange (input → Codex output, including streamed items). Use the thread APIs to create, list, or archive sessions, then drive the conversation with turn APIs and notifications.

Quick reference

  • thread/start — create a new thread; emits thread/started and auto-subscribes you to turn/item events for that thread.
  • thread/resume — reopen an existing thread by id so subsequent turn/start calls append to it.
  • thread/list — page through stored rollouts; supports cursor-based pagination and optional modelProviders filtering.
  • thread/archive — move a threads rollout file into the archived directory; returns {} on success.
  • turn/start — add user input to a thread and begin Codex generation; responds with the initial turn object and streams turn/started, item/*, and turn/completed notifications.
  • turn/interrupt — request cancellation of an in-flight turn by (thread_id, turn_id); success is an empty {} response and the turn finishes with status: "interrupted".

1) Start or resume a thread

Start a fresh thread when you need a new Codex conversation.

{ "method": "thread/start", "id": 10, "params": {
    // Optionally set config settings. If not specified, will use the user's
    // current config settings.
    "model": "gpt-5.1-codex",
    "cwd": "/Users/me/project",
    "approvalPolicy": "never",
    "sandbox": "workspaceWrite",
} }
{ "id": 10, "result": {
    "thread": {
        "id": "thr_123",
        "preview": "",
        "modelProvider": "openai",
        "createdAt": 1730910000
    }
} }
{ "method": "thread/started", "params": { "thread": {  } } }

To continue a stored session, call thread/resume with the thread.id you previously recorded. The response shape matches thread/start, and no additional notifications are emitted:

{ "method": "thread/resume", "id": 11, "params": { "threadId": "thr_123" } }
{ "id": 11, "result": { "thread": { "id": "thr_123",  } } }

2) List threads (pagination & filters)

thread/list lets you render a history UI. Pass any combination of:

  • cursor — opaque string from a prior response; omit for the first page.
  • limit — server defaults to a reasonable page size if unset.
  • modelProviders — restrict results to specific providers; unset, null, or an empty array will include all providers.

Example:

{ "method": "thread/list", "id": 20, "params": {
    "cursor": null,
    "limit": 25,
} }
{ "id": 20, "result": {
    "data": [
        { "id": "thr_a", "preview": "Create a TUI", "modelProvider": "openai", "createdAt": 1730831111 },
        { "id": "thr_b", "preview": "Fix tests", "modelProvider": "openai", "createdAt": 1730750000 }
    ],
    "nextCursor": "opaque-token-or-null"
} }

When nextCursor is null, youve reached the final page.

3) Archive a thread

Use thread/archive to move the persisted rollout (stored as a JSONL file on disk) into the archived sessions directory.

{ "method": "thread/archive", "id": 21, "params": { "threadId": "thr_b" } }
{ "id": 21, "result": {} }

An archived thread will not appear in future calls to thread/list.

4) Start a turn (send user input)

Turns attach user input (text or images) to a thread and trigger Codex generation. The input field is a list of discriminated unions:

  • {"type":"text","text":"Explain this diff"}
  • {"type":"image","url":"https://…png"}
  • {"type":"localImage","path":"/tmp/screenshot.png"}

You can optionally specify config overrides on the new turn. If specified, these settings become the default for subsequent turns on the same thread.

{ "method": "turn/start", "id": 30, "params": {
    "threadId": "thr_123",
    "input": [ { "type": "text", "text": "Run tests" } ],
    // Below are optional config overrides
    "cwd": "/Users/me/project",
    "approvalPolicy": "unlessTrusted",
    "sandboxPolicy": {
        "mode": "workspaceWrite",
        "writableRoots": ["/Users/me/project"],
        "networkAccess": true
    },
    "model": "gpt-5.1-codex",
    "effort": "medium",
    "summary": "concise"
} }
{ "id": 30, "result": { "turn": {
    "id": "turn_456",
    "status": "inProgress",
    "items": [],
    "error": null
} } }

5) Interrupt an active turn

You can cancel a running Turn with turn/interrupt.

{ "method": "turn/interrupt", "id": 31, "params": {
    "threadId": "thr_123",
    "turnId": "turn_456"
} }
{ "id": 31, "result": {} }

The server requests cancellations for running subprocesses, then emits a turn/completed event with status: "interrupted". Rely on the turn/completed to know when Codex-side cleanup is done.

Auth endpoints

The JSON-RPC auth/account surface exposes request/response methods plus server-initiated notifications (no id). Use these to determine auth state, start or cancel logins, logout, and inspect ChatGPT rate limits.

Quick reference

  • account/read — fetch current account info; optionally refresh tokens.
  • account/login/start — begin login (apiKey or chatgpt).
  • account/login/completed (notify) — emitted when a login attempt finishes (success or error).
  • account/login/cancel — cancel a pending ChatGPT login by loginId.
  • account/logout — sign out; triggers account/updated.
  • account/updated (notify) — emitted whenever auth mode changes (authMode: apikey, chatgpt, or null).
  • account/rateLimits/read — fetch ChatGPT rate limits; updates arrive via account/rateLimits/updated (notify).

1) Check auth state

Request:

{ "method": "account/read", "id": 1, "params": { "refreshToken": false } }

Response examples:

{ "id": 1, "result": { "account": null, "requiresOpenaiAuth": false } } // No OpenAI auth needed (e.g., OSS/local models)
{ "id": 1, "result": { "account": null, "requiresOpenaiAuth": true } }  // OpenAI auth required (typical for OpenAI-hosted models)
{ "id": 1, "result": { "account": { "type": "apiKey" }, "requiresOpenaiAuth": true } }
{ "id": 1, "result": { "account": { "type": "chatgpt", "email": "user@example.com", "planType": "pro" }, "requiresOpenaiAuth": true } }

Field notes:

  • refreshToken (bool): set true to force a token refresh.
  • requiresOpenaiAuth reflects the active provider; when false, Codex can run without OpenAI credentials.

2) Log in with an API key

  1. Send:
    { "method": "account/login/start", "id": 2, "params": { "type": "apiKey", "apiKey": "sk-…" } }
    
  2. Expect:
    { "id": 2, "result": { "type": "apiKey" } }
    
  3. Notifications:
    { "method": "account/login/completed", "params": { "loginId": null, "success": true, "error": null } }
    { "method": "account/updated", "params": { "authMode": "apikey" } }
    

3) Log in with ChatGPT (browser flow)

  1. Start:
    { "method": "account/login/start", "id": 3, "params": { "type": "chatgpt" } }
    { "id": 3, "result": { "type": "chatgpt", "loginId": "<uuid>", "authUrl": "https://chatgpt.com/…&redirect_uri=http%3A%2F%2Flocalhost%3A<port>%2Fauth%2Fcallback" } }
    
  2. Open authUrl in a browser; the app-server hosts the local callback.
  3. Wait for notifications:
    { "method": "account/login/completed", "params": { "loginId": "<uuid>", "success": true, "error": null } }
    { "method": "account/updated", "params": { "authMode": "chatgpt" } }
    

4) Cancel a ChatGPT login

{ "method": "account/login/cancel", "id": 4, "params": { "loginId": "<uuid>" } }
{ "method": "account/login/completed", "params": { "loginId": "<uuid>", "success": false, "error": "…" } }

5) Logout

{ "method": "account/logout", "id": 5 }
{ "id": 5, "result": {} }
{ "method": "account/updated", "params": { "authMode": null } }

6) Rate limits (ChatGPT)

{ "method": "account/rateLimits/read", "id": 6 }
{ "id": 6, "result": { "rateLimits": { "primary": { "usedPercent": 25, "windowDurationMins": 15, "resetsAt": 1730947200 }, "secondary": null } } }
{ "method": "account/rateLimits/updated", "params": { "rateLimits": {  } } }

Field notes:

  • usedPercent is current usage within the OpenAI quota window.
  • windowDurationMins is the quota window length.
  • resetsAt is a Unix timestamp (seconds) for the next reset.

Dev notes

Events (work-in-progress)

Event notifications are the server-initiated event stream for thread lifecycles, turn lifecycles, and the items within them. After you start or resume a thread, keep reading stdout for thread/started, turn/*, and item/* notifications.

Turn events

The app-server streams JSON-RPC notifications while a turn is running. Each turn starts with turn/started (initial turn) and ends with turn/completed (final turn plus token usage), and clients subscribe to the events they care about, rendering each item incrementally as updates arrive. The per-item lifecycle is always: item/started → zero or more item-specific deltas → item/completed.

Thread items

ThreadItem is the tagged union carried in turn responses and item/* notifications. Currently we support events for the following items:

  • userMessage{id, content} where content is a list of user inputs (text, image, or localImage).
  • agentMessage{id, text} containing the accumulated agent reply.
  • reasoning{id, summary, content} where summary holds streamed reasoning summaries (applicable for most OpenAI models) and content holds raw reasoning blocks (applicable for e.g. open source models).
  • mcpToolCall{id, server, tool, status, arguments, result?, error?} describing MCP calls; status is inProgress, completed, or failed.
  • webSearch{id, query} for a web search request issued by the agent.

All items emit two shared lifecycle events:

  • item/started — emits the full item when a new unit of work begins so the UI can render it immediately; the item.id in this payload matches the itemId used by deltas.
  • item/completed — sends the final item once that work finishes (e.g., after a tool call or message completes); treat this as the authoritative state.

There are additional item-specific events:

agentMessage

  • item/agentMessage/delta — appends streamed text for the agent message; concatenate delta values for the same itemId in order to reconstruct the full reply.

reasoning

  • item/reasoning/summaryTextDelta — streams readable reasoning summaries; summaryIndex increments when a new summary section opens.
  • item/reasoning/summaryPartAdded — marks the boundary between reasoning summary sections for an itemId; subsequent summaryTextDelta entries share the same summaryIndex.
  • item/reasoning/textDelta — streams raw reasoning text (only applicable for e.g. open source models); use contentIndex to group deltas that belong together before showing them in the UI.