Commit Graph

19 Commits

  • feat: show number of tokens remaining in UI (#1388)
    When using the OpenAI Responses API, we now record the `usage` field for
    a `"response.completed"` event, which includes metrics about the number
    of tokens consumed. We also introduce `openai_model_info.rs`, which
    includes current data about the most common OpenAI models available via
    the API (specifically `context_window` and `max_output_tokens`). If
    Codex does not recognize the model, you can set `model_context_window`
    and `model_max_output_tokens` explicitly in `config.toml`.
    
    When then introduce a new event type to `protocol.rs`, `TokenCount`,
    which includes the `TokenUsage` for the most recent turn.
    
    Finally, we update the TUI to record the running sum of tokens used so
    the percentage of available context window remaining can be reported via
    the placeholder text for the composer:
    
    ![Screenshot 2025-06-25 at 11 20
    55 PM](https://github.com/user-attachments/assets/6fd6982f-7247-4f14-84b2-2e600cb1fd49)
    
    We could certainly get much fancier with this (such as reporting the
    estimated cost of the conversation), but for now, we are just trying to
    achieve feature parity with the TypeScript CLI.
    
    Though arguably this improves upon the TypeScript CLI, as the TypeScript
    CLI uses heuristics to estimate the number of tokens used rather than
    using the `usage` information directly:
    
    
    https://github.com/openai/codex/blob/296996d74e345b1b05d8c3451a06ace21c5ada96/codex-cli/src/utils/approximate-tokens-used.ts#L3-L16
    
    Fixes https://github.com/openai/codex/issues/1242
  • chore: rename AskForApproval::UnlessAllowListed to AskForApproval::UnlessTrusted (#1385)
    We could just rename to `Untrusted` instead of `UnlessTrusted`, but I
    think `AskForApproval::UnlessTrusted` reads a bit better.
  • chore: rename unless-allow-listed to untrusted (#1378)
    For the `approval_policy` config option, renames `unless-allow-listed`
    to `untrusted`. In general, when it comes to exec'ing commands, I think
    "trusted" is a more accurate term than "safe."
    
    Also drops the `AskForApproval::AutoEdit` variant, as we were not really
    making use of it, anyway.
    
    Fixes https://github.com/openai/codex/issues/1250.
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/1378).
    * #1379
    * __->__ #1378
  • feat: redesign sandbox config (#1373)
    This is a major redesign of how sandbox configuration works and aims to
    fix https://github.com/openai/codex/issues/1248. Specifically, it
    replaces `sandbox_permissions` in `config.toml` (and the
    `-s`/`--sandbox-permission` CLI flags) with a "table" with effectively
    three variants:
    
    ```toml
    # Safest option: full disk is read-only, but writes and network access are disallowed.
    [sandbox]
    mode = "read-only"
    
    # The cwd of the Codex task is writable, as well as $TMPDIR on macOS.
    # writable_roots can be used to specify additional writable folders.
    [sandbox]
    mode = "workspace-write"
    writable_roots = []  # Optional, defaults to the empty list.
    network_access = false  # Optional, defaults to false.
    
    # Disable sandboxing: use at your own risk!!!
    [sandbox]
    mode = "danger-full-access"
    ```
    
    This should make sandboxing easier to reason about. While we have
    dropped support for `-s`, the way it works now is:
    
    - no flags => `read-only`
    - `--full-auto` => `workspace-write`
    - currently, there is no way to specify `danger-full-access` via a CLI
    flag, but we will revisit that as part of
    https://github.com/openai/codex/issues/1254
    
    Outstanding issue:
    
    - As noted in the `TODO` on `SandboxPolicy::is_unrestricted()`, we are
    still conflating sandbox preferences with approval preferences in that
    case, which needs to be cleaned up.
  • feat: add support for -c/--config to override individual config items (#1137)
    This PR introduces support for `-c`/`--config` so users can override
    individual config values on the command line using `--config
    name=value`. Example:
    
    ```
    codex --config model=o4-mini
    ```
    
    Making it possible to set arbitrary config values on the command line
    results in a more flexible configuration scheme and makes it easier to
    provide single-line examples that can be copy-pasted from documentation.
    
    Effectively, it means there are four levels of configuration for some
    values:
    
    - Default value (e.g., `model` currently defaults to `o4-mini`)
    - Value in `config.toml` (e.g., user could override the default to be
    `model = "o3"` in their `config.toml`)
    - Specifying `-c` or `--config` to override `model` (e.g., user can
    include `-c model=o3` in their list of args to Codex)
    - If available, a config-specific flag can be used, which takes
    precedence over `-c` (e.g., user can specify `--model o3` in their list
    of args to Codex)
    
    Now that it is possible to specify anything that could be configured in
    `config.toml` on the command line using `-c`, we do not need to have a
    custom flag for every possible config option (which can clutter the
    output of `--help`). To that end, as part of this PR, we drop support
    for the `--disable-response-storage` flag, as users can now specify `-c
    disable_response_storage=true` to get the equivalent functionality.
    
    Under the hood, this works by loading the `config.toml` into a
    `toml::Value`. Then for each `key=value`, we create a small synthetic
    TOML file with `value` so that we can run the TOML parser to get the
    equivalent `toml::Value`. We then parse `key` to determine the point in
    the original `toml::Value` to do the insert/replace. Once all of the
    overrides from `-c` args have been applied, the `toml::Value` is
    deserialized into a `ConfigToml` and then the `ConfigOverrides` are
    applied, as before.
  • fix: overhaul how we spawn commands under seccomp/landlock on Linux (#1086)
    Historically, we spawned the Seatbelt and Landlock sandboxes in
    substantially different ways:
    
    For **Seatbelt**, we would run `/usr/bin/sandbox-exec` with our policy
    specified as an arg followed by the original command:
    
    
    https://github.com/openai/codex/blob/d1de7bb383552e8fadd94be79d65d188e00fd562/codex-rs/core/src/exec.rs#L147-L219
    
    For **Landlock/Seccomp**, we would do
    `tokio::runtime::Builder::new_current_thread()`, _invoke
    Landlock/Seccomp APIs to modify the permissions of that new thread_, and
    then spawn the command:
    
    
    https://github.com/openai/codex/blob/d1de7bb383552e8fadd94be79d65d188e00fd562/codex-rs/core/src/exec_linux.rs#L28-L49
    
    While it is neat that Landlock/Seccomp supports applying a policy to
    only one thread without having to apply it to the entire process, it
    requires us to maintain two different codepaths and is a bit harder to
    reason about. The tipping point was
    https://github.com/openai/codex/pull/1061, in which we had to start
    building up the `env` in an unexpected way for the existing
    Landlock/Seccomp approach to continue to work.
    
    This PR overhauls things so that we do similar things for Mac and Linux.
    It turned out that we were already building our own "helper binary"
    comparable to Mac's `sandbox-exec` as part of the `cli` crate:
    
    
    https://github.com/openai/codex/blob/d1de7bb383552e8fadd94be79d65d188e00fd562/codex-rs/cli/Cargo.toml#L10-L12
    
    We originally created this to build a small binary to include with the
    Node.js version of the Codex CLI to provide support for Linux
    sandboxing.
    
    Though the sticky bit is that, at this point, we still want to deploy
    the Rust version of Codex as a single, standalone binary rather than a
    CLI and a supporting sandboxing binary. To satisfy this goal, we use
    "the arg0 trick," in which we:
    
    * use `std::env::current_exe()` to get the path to the CLI that is
    currently running
    * use the CLI as the `program` for the `Command`
    * set `"codex-linux-sandbox"` as arg0 for the `Command`
    
    A CLI that supports sandboxing should check arg0 at the start of the
    program. If it is `"codex-linux-sandbox"`, it must invoke
    `codex_linux_sandbox::run_main()`, which runs the CLI as if it were
    `codex-linux-sandbox`. When acting as `codex-linux-sandbox`, we make the
    appropriate Landlock/Seccomp API calls and then use `execvp(3)` to spawn
    the original command, so do _replace_ the process rather than spawn a
    subprocess. Incidentally, we do this before starting the Tokio runtime,
    so the process should only have one thread when `execvp(3)` is called.
    
    Because the `core` crate that needs to spawn the Linux sandboxing is not
    a CLI in its own right, this means that every CLI that includes `core`
    and relies on this behavior has to (1) implement it and (2) provide the
    path to the sandboxing executable. While the path is almost always
    `std::env::current_exe()`, we needed to make this configurable for
    integration tests, so `Config` now has a `codex_linux_sandbox_exe:
    Option<PathBuf>` property to facilitate threading this through,
    introduced in https://github.com/openai/codex/pull/1089.
    
    This common pattern is now captured in
    `codex_linux_sandbox::run_with_sandbox()` and all of the `main.rs`
    functions that should use it have been updated as part of this PR.
    
    The `codex-linux-sandbox` crate added to the Cargo workspace as part of
    this PR now has the bulk of the Landlock/Seccomp logic, which makes
    `core` a bit simpler. Indeed, `core/src/exec_linux.rs` and
    `core/src/landlock.rs` were removed/ported as part of this PR. I also
    moved the unit tests for this code into an integration test,
    `linux-sandbox/tests/landlock.rs`, in which I use
    `env!("CARGO_BIN_EXE_codex-linux-sandbox")` as the value for
    `codex_linux_sandbox_exe` since `std::env::current_exe()` is not
    appropriate in that case.
  • feat: add codex_linux_sandbox_exe: Option<PathBuf> field to Config (#1089)
    https://github.com/openai/codex/pull/1086 is a work-in-progress to make
    Linux sandboxing work more like Seatbelt where, for the command we want
    to sandbox, we build up the command and then hand it, and some sandbox
    configuration flags, to another command to set up the sandbox and then
    run it.
    
    In the case of Seatbelt, macOS provides this helper binary and provides
    it at `/usr/bin/sandbox-exec`. For Linux, we have to build our own and
    pass it through (which is what #1086 does), so this makes the new
    `codex_linux_sandbox_exe` available on `Config` so that it will later be
    available in `exec.rs` when we need it in #1086.
  • feat: experimental --output-last-message flag to exec subcommand (#1037)
    This introduces an experimental `--output-last-message` flag that can be
    used to identify a file where the final message from the agent will be
    written. Two use cases:
    
    - Ultimately, we will likely add a `--quiet` option to `exec`, but even
    if the user does not want any output written to the terminal, they
    probably want to know what the agent did. Writing the output to a file
    makes it possible to get that information in a clean way.
    - Relatedly, when using `exec` in CI, it is easier to review the
    transcript written "normally," (i.e., not as JSON or something with
    extra escapes), but getting programmatic access to the last message is
    likely helpful, so writing the last message to a file gets the best of
    both worlds.
    
    I am calling this "experimental" because it is possible that we are
    overfitting and will want a more general solution to this problem that
    would justify removing this flag.
  • feat: record messages from user in ~/.codex/history.jsonl (#939)
    This is a large change to support a "history" feature like you would
    expect in a shell like Bash.
    
    History events are recorded in `$CODEX_HOME/history.jsonl`. Because it
    is a JSONL file, it is straightforward to append new entries (as opposed
    to the TypeScript file that uses `$CODEX_HOME/history.json`, so to be
    valid JSON, each new entry entails rewriting the entire file). Because
    it is possible for there to be multiple instances of Codex CLI writing
    to `history.jsonl` at once, we use advisory file locking when working
    with `history.jsonl` in `codex-rs/core/src/message_history.rs`.
    
    Because we believe history is a sufficiently useful feature, we enable
    it by default. Though to provide some safety, we set the file
    permissions of `history.jsonl` to be `o600` so that other users on the
    system cannot read the user's history. We do not yet support a default
    list of `SENSITIVE_PATTERNS` as the TypeScript CLI does:
    
    
    https://github.com/openai/codex/blob/3fdf9df1335ac9501e3fb0e61715359145711e8b/codex-cli/src/utils/storage/command-history.ts#L10-L17
    
    We are going to take a more conservative approach to this list in the
    Rust CLI. For example, while `/\b[A-Za-z0-9-_]{20,}\b/` might exclude
    sensitive information like API tokens, it would also exclude valuable
    information such as references to Git commits.
    
    As noted in the updated documentation, users can opt-out of history by
    adding the following to `config.toml`:
    
    ```toml
    [history]
    persistence = "none" 
    ```
    
    Because `history.jsonl` could, in theory, be quite large, we take a[n
    arguably overly pedantic] approach in reading history entries into
    memory. Specifically, we start by telling the client the current number
    of entries in the history file (`history_entry_count`) as well as the
    inode (`history_log_id`) of `history.jsonl` (see the new fields on
    `SessionConfiguredEvent`).
    
    The client is responsible for keeping new entries in memory to create a
    "local history," but if the user hits up enough times to go "past" the
    end of local history, then the client should use the new
    `GetHistoryEntryRequest` in the protocol to fetch older entries.
    Specifically, it should pass the `history_log_id` it was given
    originally and work backwards from `history_entry_count`. (It should
    really fetch history in batches rather than one-at-a-time, but that is
    something we can improve upon in subsequent PRs.)
    
    The motivation behind this crazy scheme is that it is designed to defend
    against:
    
    * The `history.jsonl` being truncated during the session such that the
    index into the history is no longer consistent with what had been read
    up to that point. We do not yet have logic to enforce a `max_bytes` for
    `history.jsonl`, but once we do, we will aspire to implement it in a way
    that should result in a new inode for the file on most systems.
    * New items from concurrent Codex CLI sessions amending to the history.
    Because, in absence of truncation, `history.jsonl` is an append-only
    log, so long as the client reads backwards from `history_entry_count`,
    it should always get a consistent view of history. (That said, it will
    not be able to read _new_ commands from concurrent sessions, but perhaps
    we will introduce a `/` command to reload latest history or something
    down the road.)
    
    Admittedly, my testing of this feature thus far has been fairly light. I
    expect we will find bugs and introduce enhancements/fixes going forward.
  • chore: handle all cases for EventMsg (#936)
    For now, this removes the `#[non_exhaustive]` directive on `EventMsg` so
    that we are forced to handle all `EventMsg` by default. (We may revisit
    this if/when we publish `core/` as a `lib` crate.) For now, it is
    helpful to have this as a forcing function because we have effectively
    two UIs (`tui` and `exec`) and usually when we add a new variant to
    `EventMsg`, we want to be sure that we update both.
  • feat: add mcp subcommand to CLI to run Codex as an MCP server (#934)
    Previously, running Codex as an MCP server required a standalone binary
    in our Cargo workspace, but this PR makes it available as a subcommand
    (`mcp`) of the main CLI.
    
    Ran this with:
    
    ```
    RUST_LOG=debug npx @modelcontextprotocol/inspector cargo run --bin codex -- mcp
    ```
    
    and verified it worked as expected in the inspector at
    `http://127.0.0.1:6274/`.
  • fix: change EventMsg enum so every variant takes a single struct (#925)
    https://github.com/openai/codex/pull/922 did this for the
    `SessionConfigured` enum variant, and I think it is generally helpful to
    be able to work with the values as each enum variant as their own type,
    so this converts the remaining variants and updates all of the
    callsites.
    
    Added a simple unit test to verify that the JSON-serialized version of
    `Event` does not have any unexpected nesting.
  • feat: introduce --profile for Rust CLI (#921)
    This introduces a much-needed "profile" concept where users can specify
    a collection of options under one name and then pass that via
    `--profile` to the CLI.
    
    This PR introduces the `ConfigProfile` struct and makes it a field of
    `CargoToml`. It further updates
    `Config::load_from_base_config_with_overrides()` to respect
    `ConfigProfile`, overriding default values where appropriate. A detailed
    unit test is added at the end of `config.rs` to verify this behavior.
    
    Details on how to use this feature have also been added to
    `codex-rs/README.md`.
  • Disallow expect via lints (#865)
    Adds `expect()` as a denied lint. Same deal applies with `unwrap()`
    where we now need to put `#[expect(...` on ones that we legit want. Took
    care to enable `expect()` in test contexts.
    
    # Tests
    
    ```
    cargo fmt
    cargo clippy --all-features --all-targets --no-deps -- -D warnings
    cargo test
    ```
  • 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!
  • 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
  • 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.
  • 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.