Commit Graph

7 Commits

  • feat(app-server): add tracing to all app-server APIs (#13285)
    ### Overview
    This PR adds the first piece of tracing for app-server JSON-RPC
    requests.
    
    There are two main changes:
    - JSON-RPC requests can now take an optional W3C trace context at the
    top level via a `trace` field (`traceparent` / `tracestate`).
    - app-server now creates a dedicated request span for every inbound
    JSON-RPC request in `MessageProcessor`, and uses the request-level trace
    context as the parent when present.
    
    For compatibility with existing flows, app-server still falls back to
    the TRACEPARENT env var when there is no request-level traceparent.
    
    This PR is intentionally scoped to the app-server boundary. In a
    followup, we'll actually propagate trace context through the async
    handoff into core execution spans like run_turn, which will make
    app-server traces much more useful.
    
    ### Spans
    A few details on the app-server span shape:
    - each inbound request gets its own server span
    - span/resource names are based on the JSON-RPC method (`initialize`,
    `thread/start`, `turn/start`, etc.)
    - spans record transport (stdio vs websocket), request id, connection
    id, and client name/version when available
    - `initialize` stores client metadata in session state so later requests
    on the same connection can reuse it
  • app-server: Replay pending item requests on thread/resume (#12560)
    Replay pending client requests after `thread/resume` and emit resolved
    notifications when those requests clear so approval/input UI state stays
    in sync after reconnects and across subscribed clients.
    
    Affected RPCs:
    - `item/commandExecution/requestApproval`
    - `item/fileChange/requestApproval`
    - `item/tool/requestUserInput`
    
    Motivation:
    - Resumed clients need to see pending approval/input requests that were
    already outstanding before the reconnect.
    - Clients also need an explicit signal when a pending request resolves
    or is cleared so stale UI can be removed on turn start, completion, or
    interruption.
    
    Implementation notes:
    - Use pending client requests from `OutgoingMessageSender` in order to
    replay them after `thread/resume` attaches the connection, using
    original request ids.
    - Emit `serverRequest/resolved` when pending requests are answered
    or cleared by lifecycle cleanup.
    - Update the app-server protocol schema, generated TypeScript bindings,
    and README docs for the replay/resolution flow.
    
    High-level test plan:
    - Added automated coverage for replaying pending command execution and
    file change approval requests on `thread/resume`.
    - Added automated coverage for resolved notifications in command
    approval, file change approval, request_user_input, turn start, and turn
    interrupt flows.
    - Verified schema/docs updates in the relevant protocol and app-server
    tests.
    
    Manual testing:
    - Tested reconnect/resume with multiple connections.
    - Confirmed state stayed in sync between connections.
  • [app-server] remove serde(skip_serializing_if = "Option::is_none") annotations (#5939)
    We had this annotation everywhere in app-server APIs which made it so
    that fields get serialized as `field?: T`, meaning if the field as
    `None` we would omit the field in the payload. Removing this annotation
    changes it so that we return `field: T | null` instead, which makes
    codex app-server's API more aligned with the convention of public OpenAI
    APIs like Responses.
    
    Separately, remove the `#[ts(optional_fields = nullable)]` annotations
    that were recently added which made all the TS types become `field?: T |
    null` which is not great since clients need to handle undefined and
    null.
    
    I think generally it'll be best to have optional types be either:
    - `field: T | null` (preferred, aligned with public OpenAI APIs)
    - `field?: T` where we have to, such as types generated from the MCP
    schema:
    https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/2025-06-18/schema.ts
    (see changes to `mcp-types/`)
    
    I updated @etraut-openai's unit test to check that all generated TS
    types are one or the other, not both (so will error if we have a type
    that has `field?: T | null`). I don't think there's currently a good use
    case for that - but we can always revisit.
  • Add missing "nullable" macro to protocol structs that contain optional fields (#5901)
    This PR addresses a current hole in the TypeScript code generation for
    the API server protocol. Fields that are marked as "Optional<>" in the
    Rust code are serialized such that the value is omitted when it is
    deserialized — appearing as `undefined`, but the TS type indicates
    (incorrectly) that it is always defined but possibly `null`. This can
    lead to subtle errors that the TypeScript compiler doesn't catch. The
    fix is to include the `#[ts(optional_fields = nullable)]` macro for all
    protocol structs that contain one or more `Optional<>` fields.
    
    This PR also includes a new test that validates that all TS protocol
    code containing "| null" in its type is marked optional ("?") to catch
    cases where `#[ts(optional_fields = nullable)]` is omitted.
  • Generate JSON schema for app-server protocol (#5063)
    Add annotations and an export script that let us generate app-server
    protocol types as typescript and JSONSchema.
    
    The script itself is a bit hacky because we need to manually label some
    of the types. Unfortunately it seems that enum variants don't get good
    names by default and end up with something like `EventMsg1`,
    `EventMsg2`, etc. I'm not an expert in this by any means, but since this
    is only run manually and we already need to enumerate the types required
    to describe the protocol, it didn't seem that much worse. An ideal
    solution here would be to have some kind of root that we could generate
    schemas for in one go, but I'm not sure if that's compatible with how we
    generate the protocol today.
  • fix: use number instead of bigint for the generated TS for RequestId (#4575)
    Before this PR:
    
    ```typescript
    export type RequestId = string | bigint;
    ```
    
    After:
    
    ```typescript
    export type RequestId = string | number;
    ```
    
    `bigint` introduces headaches in TypeScript without providing any real
    value.
  • fix: remove mcp-types from app server protocol (#4537)
    We continue the separation between `codex app-server` and `codex
    mcp-server`.
    
    In particular, we introduce a new crate, `codex-app-server-protocol`,
    and migrate `codex-rs/protocol/src/mcp_protocol.rs` into it, renaming it
    `codex-rs/app-server-protocol/src/protocol.rs`.
    
    Because `ConversationId` was defined in `mcp_protocol.rs`, we move it
    into its own file, `codex-rs/protocol/src/conversation_id.rs`, and
    because it is referenced in a ton of places, we have to touch a lot of
    files as part of this PR.
    
    We also decide to get away from proper JSON-RPC 2.0 semantics, so we
    also introduce `codex-rs/app-server-protocol/src/jsonrpc_lite.rs`, which
    is basically the same `JSONRPCMessage` type defined in `mcp-types`
    except with all of the `"jsonrpc": "2.0"` removed.
    
    Getting rid of `"jsonrpc": "2.0"` makes our serialization logic
    considerably simpler, as we can lean heavier on serde to serialize
    directly into the wire format that we use now.