Commit Graph

3066 Commits

  • feat: support the chat completions API in the Rust CLI (#862)
    This is a substantial PR to add support for the chat completions API,
    which in turn makes it possible to use non-OpenAI model providers (just
    like in the TypeScript CLI):
    
    * It moves a number of structs from `client.rs` to `client_common.rs` so
    they can be shared.
    * It introduces support for the chat completions API in
    `chat_completions.rs`.
    * It updates `ModelProviderInfo` so that `env_key` is `Option<String>`
    instead of `String` (for e.g., ollama) and adds a `wire_api` field
    * It updates `client.rs` to choose between `stream_responses()` and
    `stream_chat_completions()` based on the `wire_api` for the
    `ModelProviderInfo`
    * It updates the `exec` and TUI CLIs to no longer fail if the
    `OPENAI_API_KEY` environment variable is not set
    * It updates the TUI so that `EventMsg::Error` is displayed more
    prominently when it occurs, particularly now that it is important to
    alert users to the `CodexErr::EnvVar` variant.
    * `CodexErr::EnvVar` was updated to include an optional `instructions`
    field so we can preserve the behavior where we direct users to
    https://platform.openai.com if `OPENAI_API_KEY` is not set.
    * Cleaned up the "welcome message" in the TUI to ensure the model
    provider is displayed.
    * Updated the docs in `codex-rs/README.md`.
    
    To exercise the chat completions API from OpenAI models, I added the
    following to my `config.toml`:
    
    ```toml
    model = "gpt-4o"
    model_provider = "openai-chat-completions"
    
    [model_providers.openai-chat-completions]
    name = "OpenAI using Chat Completions"
    base_url = "https://api.openai.com/v1"
    env_key = "OPENAI_API_KEY"
    wire_api = "chat"
    ```
    
    Though to test a non-OpenAI provider, I installed ollama with mistral
    locally on my Mac because ChatGPT said that would be a good match for my
    hardware:
    
    ```shell
    brew install ollama
    ollama serve
    ollama pull mistral
    ```
    
    Then I added the following to my `~/.codex/config.toml`:
    
    ```toml
    model = "mistral"
    model_provider = "ollama"
    ```
    
    Note this code could certainly use more test coverage, but I want to get
    this in so folks can start playing with it.
    
    For reference, I believe https://github.com/openai/codex/pull/247 was
    roughly the comparable PR on the TypeScript side.
  • fix: enable clippy on tests (#870)
    https://github.com/openai/codex/pull/855 added the clippy warning to
    disallow `unwrap()`, but apparently we were not verifying that tests
    were "clippy clean" in CI, so I ended up with a lot of local errors in
    VS Code.
    
    This turns on the check in CI and fixes the offenders.
  • fix: remove wrapping in Rust TUI that was incompatible with scrolling math (#868)
    I noticed that sometimes I would enter a new message, but it would not
    show up in the conversation history. Even if I focused the conversation
    history and tried to scroll it to the bottom, I could not bring it into
    view. At first, I was concerned that messages were not making it to the
    UI layer, but I added debug statements and verified that was not the
    issue.
    
    It turned out that, previous to this PR, lines that are wider than the
    viewport take up multiple lines of vertical space because `wrap()` was
    set on the `Paragraph` inside the scroll pane. Unfortunately, that broke
    our "scrollbar math" that assumed each `Line` contributes one line of
    height in the UI.
    
    This PR removes the `wrap()`, but introduces a new issue, which is that
    now you cannot see long lines without resizing your terminal window. For
    now, I filed an issue here:
    
    https://github.com/openai/codex/issues/869
    
    I think the long-term fix is to fix our math so it calculates the height
    of a `Line` after it is wrapped given the current width of the viewport.
  • Workspace lints and disallow unwrap (#855)
    Sets submodules to use workspace lints. Added denying unwrap as a
    workspace level lint, which found a couple of cases where we could have
    propagated errors. Also manually labeled ones that were fine by my eye.
  • feat: read model_provider and model_providers from config.toml (#853)
    This is the first step in supporting other model providers in the Rust
    CLI. Specifically, this PR adds support for the new entries in `Config`
    and `ConfigOverrides` to specify a `ModelProviderInfo`, which is the
    basic config needed for an LLM provider. This PR does not get us all the
    way there yet because `client.rs` still categorically appends
    `/responses` to the URL and expects the endpoint to support the OpenAI
    Responses API. Will fix that next!
  • fix: creating an instance of Codex requires a Config (#859)
    I discovered that I accidentally introduced a change in
    https://github.com/openai/codex/pull/829 where we load a fresh `Config`
    in the middle of `codex.rs`:
    
    
    https://github.com/openai/codex/blob/c3e10e180a341e719f61014ea508f6d9dbffe05b/codex-rs/core/src/codex.rs#L515-L522
    
    This is not good because the `Config` could differ from the one that has
    the user's overrides specified from the CLI. Also, in unit tests, it
    means the `Config` was picking up my personal settings as opposed to
    using a vanilla config, which was problematic.
    
    This PR cleans things up by moving the common case where
    `Op::ConfigureSession` is derived from `Config` (originally done in
    `codex_wrapper.rs`) and making it the standard way to initialize `Codex`
    by putting it in `Codex::spawn()`. Note this also eliminates quite a bit
    of boilerplate from the tests and relieves the caller of the
    responsibility of minting out unique IDs when invoking `submit()`.
  • fix: remove CodexBuilder and Recorder (#858)
    These abstractions were originally created exclusively for the REPL,
    which was removed in https://github.com/openai/codex/pull/754.
    Currently, the create some unnecessary Tokio tasks, so we are better off
    without them. (We can always bring this back if we have a new use case.)
  • feat: save session transcripts when using Rust CLI (#845)
    This adds support for saving transcripts when using the Rust CLI. Like
    the TypeScript CLI, it saves the transcript to `~/.codex/sessions`,
    though it uses JSONL for the file format (and `.jsonl` for the file
    extension) so that even if Codex crashes, what was written to the
    `.jsonl` file should generally still be valid JSONL content.
  • fix: add optional timeout to McpClient::send_request() (#852)
    We now impose a 10s timeout on the initial `tools/list` request to an
    MCP server. We do not apply a timeout for other types of requests yet,
    but we should start enforcing those, as well.
  • feat: introduce the use of tui-markdown (#851)
    This introduces the use of the `tui-markdown` crate to parse an
    assistant message as Markdown and style it using ANSI for a better user
    experience. As shown in the screenshot below, it has support for syntax
    highlighting for _tagged_ fenced code blocks:
    
    <img width="907" alt="image"
    src="https://github.com/user-attachments/assets/900dc229-80bb-46e8-b1bb-efee4c70ba3c"
    />
    
    That said, `tui-markdown` is not as configurable (or stylish!) as
    https://www.npmjs.com/package/marked-terminal, which is what we use in
    the TypeScript CLI. In particular:
    
    * The styles are hardcoded and `tui_markdown::from_str()` does not take
    any options whatsoever. It uses "bold white" for inline code style which
    does not stand out as much as the yellow used by `marked-terminal`:
    
    
    https://github.com/joshka/tui-markdown/blob/65402cbda70325f34e7ddf6fe1ec629bcd9459cf/tui-markdown/src/lib.rs#L464
    
    I asked Codex to take a first pass at this and it came up with:
    
    https://github.com/joshka/tui-markdown/pull/80
    
    * If a fenced code block is not tagged, then it does not get
    highlighted. I would rather add some logic here:
    
    
    https://github.com/joshka/tui-markdown/blob/65402cbda70325f34e7ddf6fe1ec629bcd9459cf/tui-markdown/src/lib.rs#L262
    
    that uses something like https://pypi.org/project/guesslang/ to examine
    the value of `text` and try to use the appropriate syntax highlighter.
    
    * When we have a fenced code block, we do not want to show the opening
    and closing triple backticks in the output.
    
    To unblock ourselves, we might want to bundle our own fork of
    `tui-markdown` temporarily until we figure out what the shape of the API
    should be and then try to upstream it.
  • Update submodules version to come from the workspace (#850)
    Tie the version of submodules to the workspace version.
  • Update cargo to 2024 edition (#842)
    Some effects of this change:
    - New formatting changes across many files. No functionality changes
    should occur from that.
    - Calls to `set_env` are considered unsafe, since this only happens in
    tests we wrap them in `unsafe` blocks
  • chore: introduce codex-common crate (#843)
    I started this PR because I wanted to share the `format_duration()`
    utility function in `codex-rs/exec/src/event_processor.rs` with the TUI.
    The question was: where to put it?
    
    `core` should have as few dependencies as possible, so moving it there
    would introduce a dependency on `chrono`, which seemed undesirable.
    `core` already had this `cli` feature to deal with a similar situation
    around sharing common utility functions, so I decided to:
    
    * make `core` feature-free
    * introduce `common`
    * `common` can have as many "special interest" features as it needs,
    each of which can declare their own deps
    * the first two features of common are `cli` and `elapsed`
    
    In practice, this meant updating a number of `Cargo.toml` files,
    replacing this line:
    
    ```toml
    codex-core = { path = "../core", features = ["cli"] }
    ```
    
    with these:
    
    ```toml
    codex-core = { path = "../core" }
    codex-common = { path = "../common", features = ["cli"] }
    ```
    
    Moving `format_duration()` into its own file gave it some "breathing
    room" to add a unit test, so I had Codex generate some tests and new
    support for durations over 1 minute.
  • feat: show MCP tool calls in codex exec subcommand (#841)
    This is analogous to the change for the TUI in
    https://github.com/openai/codex/pull/836, but for `codex exec`.
    
    To test, I ran:
    
    ```
    cargo run --bin codex-exec -- 'what is the weather in wellesley ma tomorrow'
    ```
    
    and saw:
    
    
    ![image](https://github.com/user-attachments/assets/5714e07f-88c7-4dd9-aa0d-be54c1670533)
  • feat: drop support for q in the Rust TUI since we already support ctrl+d (#799)
    Out of the box, we will make `/` the only official "escape sequence" for
    commands in the Rust TUI. We will look to support `q` (or any string you
    want to use as a "macro") via a plugin, but not make it part of the
    default experience.
    
    Existing `q` users will have to get by with `ctrl+d` for now.
  • fix: make all fields of Session struct private again (#840)
    https://github.com/openai/codex/pull/829 noted it introduced a circular
    dep between `codex.rs` and `mcp_tool_call.rs`. This attempts to clean
    things up: the circular dep still exists, but at least all the fields of
    `Session` are private again.
  • feat: show MCP tool calls in TUI (#836)
    Adds logic for the `McpToolCallBegin` and `McpToolCallEnd` events in
    `codex-rs/tui/src/chatwidget.rs` so they get entries in the conversation
    history in the TUI.
    
    Building on top of https://github.com/openai/codex/pull/829, here is the
    result of running:
    
    ```
    cargo run --bin codex -- 'what is the weather in san francisco tomorrow'
    ```
    
    
    ![image](https://github.com/user-attachments/assets/db4a79bb-4988-46cb-acb2-446d5ba9e058)
  • feat: support mcp_servers in config.toml (#829)
    This adds initial support for MCP servers in the style of Claude Desktop
    and Cursor. Note this PR is the bare minimum to get things working end
    to end: all configured MCP servers are launched every time Codex is run,
    there is no recovery for MCP servers that crash, etc.
    
    (Also, I took some shortcuts to change some fields of `Session` to be
    `pub(crate)`, which also means there are circular deps between
    `codex.rs` and `mcp_tool_call.rs`, but I will clean that up in a
    subsequent PR.)
    
    `codex-rs/README.md` is updated as part of this PR to explain how to use
    this feature. There is a bit of plumbing to route the new settings from
    `Config` to the business logic in `codex.rs`. The most significant
    chunks for new code are in `mcp_connection_manager.rs` (which defines
    the `McpConnectionManager` struct) and `mcp_tool_call.rs`, which is
    responsible for tool calls.
    
    This PR also introduces new `McpToolCallBegin` and `McpToolCallEnd`
    event types to the protocol, but does not add any handlers for them.
    (See https://github.com/openai/codex/pull/836 for initial usage.)
    
    To test, I added the following to my `~/.codex/config.toml`:
    
    ```toml
    # Local build of https://github.com/hideya/mcp-server-weather-js
    [mcp_servers.weather]
    command = "/Users/mbolin/code/mcp-server-weather-js/dist/index.js"
    args = []
    ```
    
    And then I ran the following:
    
    ```
    codex-rs$ cargo run --bin codex exec 'what is the weather in san francisco'
    [2025-05-06T22:40:05] Task started: 1
    [2025-05-06T22:40:18] Agent message: Here’s the latest National Weather Service forecast for San Francisco (downtown, near 37.77° N, 122.42° W):
    
    This Afternoon (Tue):
    • Sunny, high near 69 °F
    • West-southwest wind around 12 mph
    
    Tonight:
    • Partly cloudy, low around 52 °F
    • SW wind 7–10 mph
    ...
    ```
    
    Note that Codex itself is not able to make network calls, so it would
    not normally be able to get live weather information like this. However,
    the weather MCP is [currently] not run under the Codex sandbox, so it is
    able to hit `api.weather.gov` and fetch current weather information.
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/829).
    * #836
    * __->__ #829
  • fix: build all crates individually as part of CI (#833)
    I discovered that `cargo build` worked for the entire workspace, but not
    for the `mcp-client` or `core` crates.
    
    * `mcp-client` failed to build because it underspecified the set of
    features it needed from `tokio`.
    * `core` failed to build because it was using a "feature" of its own
    crate in the default, no-feature version.
     
    This PR fixes the builds and adds a check in CI to defend against this
    sort of thing going forward.
  • feat: update McpClient::new_stdio_client() to accept an env (#831)
    Cleans up the signature for `new_stdio_client()` to more closely mirror
    how MCP servers are declared in config files (`command`, `args`, `env`).
    Also takes a cue from Claude Code where the MCP server is launched with
    a restricted `env` so that it only includes "safe" things like `USER`
    and `PATH` (see the `create_env_for_mcp_server()` function introduced in
    this PR for details) by default, as it is common for developers to have
    sensitive API keys present in their environment that should only be
    forwarded to the MCP server when the user has explicitly configured it
    to do so.
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/831).
    * #829
    * __->__ #831
  • feat: initial McpClient for Rust (#822)
    This PR introduces an initial `McpClient` that we will use to give Codex
    itself programmatic access to foreign MCPs. This does not wire it up in
    Codex itself yet, but the new `mcp-client` crate includes a `main.rs`
    for basic testing for now.
    
    Manually tested by sending a `tools/list` request to Codex's own MCP
    server:
    
    ```
    codex-rs$ cargo build
    codex-rs$ cargo run --bin codex-mcp-client ./target/debug/codex-mcp-server
    {
      "tools": [
        {
          "description": "Run a Codex session. Accepts configuration parameters matching the Codex Config struct.",
          "inputSchema": {
            "properties": {
              "approval-policy": {
                "description": "Execution approval policy expressed as the kebab-case variant name (`unless-allow-listed`, `auto-edit`, `on-failure`, `never`).",
                "enum": [
                  "auto-edit",
                  "unless-allow-listed",
                  "on-failure",
                  "never"
                ],
                "type": "string"
              },
              "cwd": {
                "description": "Working directory for the session. If relative, it is resolved against the server process's current working directory.",
                "type": "string"
              },
              "disable-response-storage": {
                "description": "Disable server-side response storage.",
                "type": "boolean"
              },
              "model": {
                "description": "Optional override for the model name (e.g. \"o3\", \"o4-mini\")",
                "type": "string"
              },
              "prompt": {
                "description": "The *initial user prompt* to start the Codex conversation.",
                "type": "string"
              },
              "sandbox-permissions": {
                "description": "Sandbox permissions using the same string values accepted by the CLI (e.g. \"disk-write-cwd\", \"network-full-access\").",
                "items": {
                  "enum": [
                    "disk-full-read-access",
                    "disk-write-cwd",
                    "disk-write-platform-user-temp-folder",
                    "disk-write-platform-global-temp-folder",
                    "disk-full-write-access",
                    "network-full-access"
                  ],
                  "type": "string"
                },
                "type": "array"
              }
            },
            "required": [
              "prompt"
            ],
            "type": "object"
          },
          "name": "codex"
        }
      ]
    }
    ```
  • feat: make Codex available as a tool when running it as an MCP server (#811)
    This PR replaces the placeholder `"echo"` tool call in the MCP server
    with a `"codex"` tool that calls Codex. Events such as
    `ExecApprovalRequest` and `ApplyPatchApprovalRequest` are not handled
    properly yet, but I have `approval_policy = "never"` set in my
    `~/.codex/config.toml` such that those codepaths are not exercised.
    
    The schema for this MPC tool is defined by a new `CodexToolCallParam`
    struct introduced in this PR. It is fairly similar to `ConfigOverrides`,
    as the param is used to help create the `Config` used to start the Codex
    session, though it also includes the `prompt` used to kick off the
    session.
    
    This PR also introduces the use of the third-party `schemars` crate to
    generate the JSON schema, which is verified in the
    `verify_codex_tool_json_schema()` unit test.
    
    Events that are dispatched during the Codex session are sent back to the
    MCP client as MCP notifications. This gives the client a way to monitor
    progress as the tool call itself may take minutes to complete depending
    on the complexity of the task requested by the user.
    
    In the video below, I launched the server via:
    
    ```shell
    mcp-server$ RUST_LOG=debug npx @modelcontextprotocol/inspector cargo run --
    ```
    
    In the video, you can see the flow of:
    
    * requesting the list of tools
    * choosing the **codex** tool
    * entering a value for **prompt** and then making the tool call
    
    Note that I left the other fields blank because when unspecified, the
    values in my `~/.codex/config.toml` were used:
    
    
    https://github.com/user-attachments/assets/1975058c-b004-43ef-8c8d-800a953b8192
    
    Note that while using the inspector, I did run into
    https://github.com/modelcontextprotocol/inspector/issues/293, though the
    tip about ensuring I had only one instance of the **MCP Inspector** tab
    open in my browser seemed to fix things.
  • fix: ensure apply_patch resolves relative paths against workdir or project cwd (#810)
    https://github.com/openai/codex/pull/800 kicked off some work to be more
    disciplined about honoring the `cwd` param passed in rather than
    assuming `std::env::current_dir()` as the `cwd`. As part of this, we
    need to ensure `apply_patch` calls honor the appropriate `cwd` as well,
    which is significant if the paths in the `apply_patch` arg are not
    absolute paths themselves. Failing that:
    
    - The `apply_patch` function call can contain an optional`workdir`
    param, so:
    - If specified and is an absolute path, it should be used to resolve
    relative paths
    - If specified and is a relative path, should be resolved against
    `Config.cwd` and then any relative paths will be resolved against the
    result
    - If `workdir` is not specified on the function call, relative paths
    should be resolved against `Config.cwd`
    
    Note that we had a similar issue in the TypeScript CLI that was fixed in
    https://github.com/openai/codex/pull/556.
    
    As part of the fix, this PR introduces `ApplyPatchAction` so clients can
    deal with that instead of the raw `HashMap<PathBuf,
    ApplyPatchFileChange>`. This enables us to enforce, by construction,
    that all paths contained in the `ApplyPatchAction` are absolute paths.
  • fix: is_inside_git_repo should take the directory as a param (#809)
    https://github.com/openai/codex/pull/800 made `cwd` a property of
    `Config` and made it so the `cwd` is not necessarily
    `std::env::current_dir()`. As such, `is_inside_git_repo()` should check
    `Config.cwd` rather than `std::env::current_dir()`.
    
    This PR updates `is_inside_git_repo()` to take `Config` instead of an
    arbitrary `PathBuf` to force the check to operate on a `Config` where
    `cwd` has been resolved to what the user specified.
  • fix: TUI should use cwd from Config (#808)
    https://github.com/openai/codex/pull/800 made `cwd` a property of
    `Config`, so the TUI should use this instead of running
    `std::env::current_dir()`.
  • feat: make cwd a required field of Config so we stop assuming std::env::current_dir() in a session (#800)
    In order to expose Codex via an MCP server, I realized that we should be
    taking `cwd` as a parameter rather than assuming
    `std::env::current_dir()` as the `cwd`. Specifically, the user may want
    to start a session in a directory other than the one where the MCP
    server has been started.
    
    This PR makes `cwd: PathBuf` a required field of `Session` and threads
    it all the way through, though I think there is still an issue with not
    honoring `workdir` for `apply_patch`, which is something we also had to
    fix in the TypeScript version: https://github.com/openai/codex/pull/556.
    
    This also adds `-C`/`--cd` to change the cwd via the command line.
    
    To test, I ran:
    
    ```
    cargo run --bin codex -- exec -C /tmp 'show the output of ls'
    ```
    
    and verified it showed the contents of my `/tmp` folder instead of
    `$PWD`.
  • doc: update the config.toml documentation for the Rust CLI in codex-rs/README.md (#795)
    https://github.com/openai/codex/pull/793 had important information on
    the `notify` config option that seemed worth memorializing, so this PR
    updates the documentation about all of the configurable options in
    `~/.codex/config.toml`.
  • feat: configurable notifications in the Rust CLI (#793)
    With this change, you can specify a program that will be executed to get
    notified about events generated by Codex. The notification info will be
    packaged as a JSON object. The supported notification types are defined
    by the `UserNotification` enum introduced in this PR. Initially, it
    contains only one variant, `AgentTurnComplete`:
    
    ```rust
    pub(crate) enum UserNotification {
        #[serde(rename_all = "kebab-case")]
        AgentTurnComplete {
            turn_id: String,
    
            /// Messages that the user sent to the agent to initiate the turn.
            input_messages: Vec<String>,
    
            /// The last message sent by the assistant in the turn.
            last_assistant_message: Option<String>,
        },
    }
    ```
    
    This is intended to support the common case when a "turn" ends, which
    often means it is now your chance to give Codex further instructions.
    
    For example, I have the following in my `~/.codex/config.toml`:
    
    ```toml
    notify = ["python3", "/Users/mbolin/.codex/notify.py"]
    ```
    
    I created my own custom notifier script that calls out to
    [terminal-notifier](https://github.com/julienXX/terminal-notifier) to
    show a desktop push notification on macOS. Contents of `notify.py`:
    
    ```python
    #!/usr/bin/env python3
    
    import json
    import subprocess
    import sys
    
    
    def main() -> int:
        if len(sys.argv) != 2:
            print("Usage: notify.py <NOTIFICATION_JSON>")
            return 1
    
        try:
            notification = json.loads(sys.argv[1])
        except json.JSONDecodeError:
            return 1
    
        match notification_type := notification.get("type"):
            case "agent-turn-complete":
                assistant_message = notification.get("last-assistant-message")
                if assistant_message:
                    title = f"Codex: {assistant_message}"
                else:
                    title = "Codex: Turn Complete!"
                input_messages = notification.get("input_messages", [])
                message = " ".join(input_messages)
                title += message
            case _:
                print(f"not sending a push notification for: {notification_type}")
                return 0
    
        subprocess.check_output(
            [
                "terminal-notifier",
                "-title",
                title,
                "-message",
                message,
                "-group",
                "codex",
                "-ignoreDnD",
                "-activate",
                "com.googlecode.iterm2",
            ]
        )
    
        return 0
    
    
    if __name__ == "__main__":
        sys.exit(main())
    ```
    
    For reference, here are related PRs that tried to add this functionality
    to the TypeScript version of the Codex CLI:
    
    * https://github.com/openai/codex/pull/160
    * https://github.com/openai/codex/pull/498
  • feat: introduce mcp-server crate (#792)
    This introduces the `mcp-server` crate, which contains a barebones MCP
    server that provides an `echo` tool that echoes the user's request back
    to them.
    
    To test it out, I launched
    [modelcontextprotocol/inspector](https://github.com/modelcontextprotocol/inspector)
    like so:
    
    ```
    mcp-server$ npx @modelcontextprotocol/inspector cargo run --
    ```
    
    and opened up `http://127.0.0.1:6274` in my browser:
    
    
    ![image](https://github.com/user-attachments/assets/83fc55d4-25c2-4497-80cd-e9702283ff93)
    
    I also had to make a small fix to `mcp-types`, adding
    `#[serde(untagged)]` to a number of `enum`s.
  • fix: mcp-types serialization wasn't quite working (#791)
    While creating a basic MCP server in
    https://github.com/openai/codex/pull/792, I discovered a number of bugs
    with the initial `mcp-types` crate that I needed to fix in order to
    implement the server.
    
    For example, I discovered that when serializing a message, `"jsonrpc":
    "2.0"` was not being included.
    
    I changed the codegen so that the field is added as:
    
    ```rust
        #[serde(rename = "jsonrpc", default = "default_jsonrpc")]
        pub jsonrpc: String,
    ```
    
    This ensures that the field is serialized as `"2.0"`, though the field
    still has to be assigned, which is tedious. I may experiment with
    `Default` or something else in the future. (I also considered creating a
    custom serializer, but I'm not sure it's worth the trouble.)
    
    While here, I also added `MCP_SCHEMA_VERSION` and `JSONRPC_VERSION` as
    `pub const`s for the crate.
    
    I also discovered that MCP rejects sending `null` for optional fields,
    so I had to add `#[serde(skip_serializing_if = "Option::is_none")]` on
    `Option` fields.
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/791).
    * #792
    * __->__ #791
  • feat: introduce mcp-types crate (#787)
    This adds our own `mcp-types` crate to our Cargo workspace. We vendor in
    the
    [`2025-03-26/schema.json`](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/05f204513641c05bd78d056791af99c6c84520fa/schema/2025-03-26/schema.json)
    from the MCP repo and introduce a `generate_mcp_types.py` script to
    codegen the `lib.rs` from the JSON schema.
    
    Test coverage is currently light, but I plan to refine things as we
    start making use of this crate.
    
    And yes, I am aware that
    https://github.com/modelcontextprotocol/rust-sdk exists, though the
    published https://crates.io/crates/rmcp appears to be a competing
    effort. While things are up in the air, it seems better for us to
    control our own version of this code.
    
    Incidentally, Codex did a lot of the work for this PR. I told it to
    never edit `lib.rs` directly and instead to update
    `generate_mcp_types.py` and then re-run it to update `lib.rs`. It
    followed these instructions and once things were working end-to-end, I
    iteratively asked for changes to the tests until the API looked
    reasonable (and the code worked). Codex was responsible for figuring out
    what to do to `generate_mcp_types.py` to achieve the requested test/API
    changes.
  • chore: script to create a Rust release (#759)
    For now, keep things simple such that we never update the `version` in
    the `Cargo.toml` for the workspace root on the `main` branch. Instead,
    create a new branch for a release, push one commit that updates the
    `version`, and then tag that branch to kick off a release.
    
    To test, I ran this script and created this release job:
    
    https://github.com/openai/codex/actions/runs/14762580641
  • chore: Rust release, set prerelease:false and version=0.0.2504301132 (#755)
    The generated DotSlash file has URLs that refer to
    `https://github.com/openai/codex/releases/`, so let's set
    `prerelease:false` (but keep `draft:true` for now) so those URLs should
    work.
    
    Also updated `version` in Cargo workspace so I will kick off a build
    once this lands.
  • chore: remove the REPL crate/subcommand (#754)
    @oai-ragona and I discussed it, and we feel the REPL crate has served
    its purpose, so we're going to delete the code and future archaeologists
    can find it in Git history.
  • chore: fix errors in .github/workflows/rust-release.yml and prep 0.0.2504292006 release (#745)
    Apparently I made two key mistakes in
    https://github.com/openai/codex/pull/740 (fixed in this PR):
    
    * I forgot to redefine `$dest` in the `Stage Linux-only artifacts` step
    * I did not define the `if` check correctly in the `Stage Linux-only
    artifacts` step
    
    This fixes both of those issues and bumps the workspace version to
    `0.0.2504292006` in preparation for another release attempt.
  • feat: codex-linux-sandbox standalone executable (#740)
    This introduces a standalone executable that run the equivalent of the
    `codex debug landlock` subcommand and updates `rust-release.yml` to
    include it in the release.
    
    The idea is that we will include this small binary with the TypeScript
    CLI to provide support for Linux sandboxing.
  • [codex-rs] Add rust-release action (#671)
    Taking a pass at building artifacts per platform so we can consider
    different distribution strategies that don't require users to install
    the full `cargo` toolchain.
    
    Right now this grabs just the `codex-repl` and `codex-tui` bins for 5
    different targets and bundles them into a draft release. I think a
    clearly marked pre-release set of artifacts will unblock the next step
    of testing.
  • fix: overhaul SandboxPolicy and config loading in Rust (#732)
    Previous to this PR, `SandboxPolicy` was a bit difficult to work with:
    
    
    https://github.com/openai/codex/blob/237f8a11e11fdcc793a09e787e48215676d9b95b/codex-rs/core/src/protocol.rs#L98-L108
    
    Specifically:
    
    * It was an `enum` and therefore options were mutually exclusive as
    opposed to additive.
    * It defined things in terms of what the agent _could not_ do as opposed
    to what they _could_ do. This made things hard to support because we
    would prefer to build up a sandbox config by starting with something
    extremely restrictive and only granting permissions for things the user
    as explicitly allowed.
    
    This PR changes things substantially by redefining the policy in terms
    of two concepts:
    
    * A `SandboxPermission` enum that defines permissions that can be
    granted to the agent/sandbox.
    * A `SandboxPolicy` that internally stores a `Vec<SandboxPermission>`,
    but externally exposes a simpler API that can be used to configure
    Seatbelt/Landlock.
    
    Previous to this PR, we supported a `--sandbox` flag that effectively
    mapped to an enum value in `SandboxPolicy`. Though now that
    `SandboxPolicy` is a wrapper around `Vec<SandboxPermission>`, the single
    `--sandbox` flag no longer makes sense. While I could have turned it
    into a flag that the user can specify multiple times, I think the
    current values to use with such a flag are long and potentially messy,
    so for the moment, I have dropped support for `--sandbox` altogether and
    we can bring it back once we have figured out the naming thing.
    
    Since `--sandbox` is gone, users now have to specify `--full-auto` to
    get a sandbox that allows writes in `cwd`. Admittedly, there is no clean
    way to specify the equivalent of `--full-auto` in your `config.toml`
    right now, so we will have to revisit that, as well.
    
    Because `Config` presents a `SandboxPolicy` field and `SandboxPolicy`
    changed considerably, I had to overhaul how config loading works, as
    well. There are now two distinct concepts, `ConfigToml` and `Config`:
    
    * `ConfigToml` is the deserialization of `~/.codex/config.toml`. As one
    might expect, every field is `Optional` and it is `#[derive(Deserialize,
    Default)]`. Consistent use of `Optional` makes it clear what the user
    has specified explicitly.
    * `Config` is the "normalized config" and is produced by merging
    `ConfigToml` with `ConfigOverrides`. Where `ConfigToml` contains a raw
    `Option<Vec<SandboxPermission>>`, `Config` presents only the final
    `SandboxPolicy`.
    
    The changes to `core/src/exec.rs` and `core/src/linux.rs` merit extra
    special attention to ensure we are faithfully mapping the
    `SandboxPolicy` to the Seatbelt and Landlock configs, respectively.
    
    Also, take note that `core/src/seatbelt_readonly_policy.sbpl` has been
    renamed to `codex-rs/core/src/seatbelt_base_policy.sbpl` and that
    `(allow file-read*)` has been removed from the `.sbpl` file as now this
    is added to the policy in `core/src/exec.rs` when
    `sandbox_policy.has_full_disk_read_access()` is `true`.
  • fix: eliminate runtime dependency on patch(1) for apply_patch (#718)
    When processing an `apply_patch` tool call, we were already computing
    the new file content in order to compute the unified diff. Before this
    PR, we were shelling out to `patch(1)` to apply the unified diff once
    the user accepted the change, but this updates the code to just retain
    the new file content and use it to write the file when the user accepts.
    This simplifies deployment because it no longer assumes `patch(1)` is on
    the host.
    
    Note this change is internal to the Codex agent and does not affect
    `protocol.rs`.
  • feat: add debug landlock subcommand comparable to debug seatbelt (#715)
    This PR adds a `debug landlock` subcommand to the Codex CLI for testing
    how Codex would execute a command using the specified sandbox policy.
    
    Built and ran this code in the `rust:latest` Docker container. In the
    container, hitting the network with vanilla `curl` succeeds:
    
    ```
    $ curl google.com
    <HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
    <TITLE>301 Moved</TITLE></HEAD><BODY>
    <H1>301 Moved</H1>
    The document has moved
    <A HREF="http://www.google.com/">here</A>.
    </BODY></HTML>
    ```
    
    whereas this fails, as expected:
    
    ```
    $ cargo run -- debug landlock -s network-restricted -- curl google.com
    curl: (6) getaddrinfo() thread failed to start
    ```
  • feat: make it possible to set disable_response_storage = true in config.toml (#714)
    https://github.com/openai/codex/pull/642 introduced support for the
    `--disable-response-storage` flag, but if you are a ZDR customer, it is
    tedious to set this every time, so this PR makes it possible to set this
    once in `config.toml` and be done with it.
    
    Incidentally, this tidies things up such that now `init_codex()` takes
    only one parameter: `Config`.
  • fix: make the TUI the default/"interactive" CLI in Rust (#711)
    Originally, the `interactive` crate was going to be a placeholder for
    building out a UX that was comparable to that of the existing TypeScript
    CLI. Though after researching how Ratatui works, that seems difficult to
    do because it is designed around the idea that it will redraw the full
    screen buffer each time (and so any scrolling should be "internal" to
    your Ratatui app) whereas the TypeScript CLI expects to render the full
    history of the conversation every time(*) (which is why you can use your
    terminal scrollbar to scroll it).
    
    While it is possible to use Ratatui in a way that acts more like what
    the TypeScript CLI is doing, it is awkward and seemingly results in
    tedious code, so I think we should abandon that approach. As such, this
    PR deletes the `interactive/` folder and the code that depended on it.
    
    Further, since we added support for mousewheel scrolling in the TUI in
    https://github.com/openai/codex/pull/641, it certainly feels much better
    and the need for scroll support via the terminal scrollbar is greatly
    diminished. This is now a more appropriate default UX for the
    "multitool" CLI.
    
    (*) Incidentally, I haven't verified this, but I think this results in
    O(N^2) work in rendering, which seems potentially problematic for long
    conversations.
  • fix: tighten up check for /usr/bin/sandbox-exec (#710)
    * In both TypeScript and Rust, we now invoke `/usr/bin/sandbox-exec`
    explicitly rather than whatever `sandbox-exec` happens to be on the
    `PATH`.
    * Changed `isSandboxExecAvailable` to use `access()` rather than
    `command -v` so that:
      *  We only do the check once over the lifetime of the Codex process.
      * The check is specific to `/usr/bin/sandbox-exec`.
    * We now do a syscall rather than incur the overhead of spawning a
    process, dealing with timeouts, etc.
    
    I think there is still room for improvement here where we should move
    the `isSandboxExecAvailable` check earlier in the CLI, ideally right
    after we do arg parsing to verify that we can provide the Seatbelt
    sandbox if that is what the user has requested.