Commit Graph

139 Commits

  • codex-rs: make tool calls prettier (#1211)
    This PR overhauls how active tool calls and completed tool calls are
    displayed:
    
    1. More use of colour to indicate success/failure and distinguish
    between components like tool name+arguments
    2. Previously, the entire `CallToolResult` was serialized to JSON and
    pretty-printed. Now, we extract each individual `CallToolResultContent`
    and print those
    1. The previous solution was wasting space by unnecessarily showing
    details of the `CallToolResult` struct to users, without formatting the
    actual tool call results nicely
    2. We're now able to show users more information from tool results in
    less space, with nicer formatting when tools return JSON results
    
    ### Before:
    
    <img width="1251" alt="Screenshot 2025-06-03 at 11 24 26"
    src="https://github.com/user-attachments/assets/5a58f222-219c-4c53-ace7-d887194e30cf"
    />
    
    ### After:
    
    <img width="1265" alt="image"
    src="https://github.com/user-attachments/assets/99fe54d0-9ebe-406a-855b-7aa529b91274"
    />
    
    ## Future Work
    
    1. Integrate image tool result handling better. We should be able to
    display images even if they're not the first `CallToolResultContent`
    2. Users should have some way to view the full version of truncated tool
    results
    3. It would be nice to add some left padding for tool results, make it
    more clear that they are results. This is doable, just a little fiddly
    due to the way `first_visible_line` scrolling works
    4. There's almost certainly a better way to format JSON than "all on 1
    line with spaces to make Ratatui wrapping work". But I think that works
    OK for now.
  • fix: always send full instructions when using the Responses API (#1207)
    This fixes a longstanding error in the Rust CLI where `codex.rs`
    contained an errant `is_first_turn` check that would exclude the user
    instructions for subsequent "turns" of a conversation when using the
    responses API (i.e., when `previous_response_id` existed).
    
    While here, renames `Prompt.instructions` to `Prompt.user_instructions`
    since we now have quite a few levels of instructions floating around.
    Also removed an unnecessary use of `clone()` in
    `Prompt.get_full_instructions()`.
  • fix: provide tolerance for apply_patch tool (#993)
    As explained in detail in the doc comment for `ParseMode::Lenient`, we
    have observed that GPT-4.1 does not always generate a valid invocation
    of `apply_patch`. Fortunately, the error is predictable, so we introduce
    some new logic to the `codex-apply-patch` crate to recover from this
    error.
    
    Because we would like to avoid this becoming a de facto standard (as it
    would be incompatible if `apply_patch` were provided as an actual
    executable, unless we also introduced the lenient behavior in the
    executable, as well), we require passing `ParseMode::Lenient` to
    `parse_patch_text()` to make it clear that the caller is opting into
    supporting this special case.
    
    Note the analogous change to the TypeScript CLI was
    https://github.com/openai/codex/pull/930. In addition to changing the
    accepted input to `apply_patch`, it also introduced additional
    instructions for the model, which we include in this PR.
    
    Note that `apply-patch` does not depend on either `regex` or
    `regex-lite`, so some of the checks are slightly more verbose to avoid
    introducing this dependency.
    
    That said, this PR does not leverage the existing
    `extract_heredoc_body_from_apply_patch_command()`, which depends on
    `tree-sitter` and `tree-sitter-bash`:
    
    
    https://github.com/openai/codex/blob/5a5aa899143f9b9ef606692c401b010368b15bdb/codex-rs/apply-patch/src/lib.rs#L191-L246
    
    though perhaps it should.
  • chore: replace regex with regex-lite, where appropriate (#1200)
    As explained on https://crates.io/crates/regex-lite, `regex-lite` is a
    lighter alternative to `regex` and seems to be sufficient for our
    purposes.
  • feat: make reasoning effort/summaries configurable (#1199)
    Previous to this PR, we always set `reasoning` when making a request
    using the Responses API:
    
    
    https://github.com/openai/codex/blob/d7245cbbc9d8ff5446da45e5951761103492476d/codex-rs/core/src/client.rs#L108-L111
    
    Though if you tried to use the Rust CLI with `--model gpt-4.1`, this
    would fail with:
    
    ```shell
    "Unsupported parameter: 'reasoning.effort' is not supported with this model."
    ```
    
    We take a cue from the TypeScript CLI, which does a check on the model
    name:
    
    
    https://github.com/openai/codex/blob/d7245cbbc9d8ff5446da45e5951761103492476d/codex-cli/src/utils/agent/agent-loop.ts#L786-L789
    
    This PR does a similar check, though also adds support for the following
    config options:
    
    ```
    model_reasoning_effort = "low" | "medium" | "high" | "none"
    model_reasoning_summary = "auto" | "concise" | "detailed" | "none"
    ```
    
    This way, if you have a model whose name happens to start with `"o"` (or
    `"codex"`?), you can set these to `"none"` to explicitly disable
    reasoning, if necessary. (That said, it seems unlikely anyone would use
    the Responses API with non-OpenAI models, but we provide an escape
    hatch, anyway.)
    
    This PR also updates both the TUI and `codex exec` to show `reasoning
    effort` and `reasoning summaries` in the header.
  • fix: chat completions API now also passes tools along (#1167)
    Prior to this PR, there were two big misses in `chat_completions.rs`:
    
    1. The loop in `stream_chat_completions()` was only including items of
    type `ResponseItem::Message` when building up the `"messages"` JSON for
    the `POST` request to the `chat/completions` endpoint. This fixes things
    by ensuring other variants (`FunctionCall`, `LocalShellCall`, and
    `FunctionCallOutput`) are included, as well.
    2. In `process_chat_sse()`, we were not recording tool calls and were
    only emitting items of type
    `ResponseEvent::OutputItemDone(ResponseItem::Message)` to the stream.
    Now we introduce `FunctionCallState`, which is used to accumulate the
    `delta`s of type `tool_calls`, so we can ultimately emit a
    `ResponseItem::FunctionCall`, when appropriate.
    
    While function calling now appears to work for chat completions with my
    local testing, I believe that there are still edge cases that are not
    covered and that this codepath would benefit from a battery of
    integration tests. (As part of that further cleanup, we should also work
    to support streaming responses in the UI.)
    
    The other important part of this PR is some cleanup in
    `core/src/codex.rs`. In particular, it was hard to reason about how
    `run_task()` was building up the list of messages to include in a
    request across the various cases:
    
    - Responses API
    - Chat Completions API
    - Responses API used in concert with ZDR
    
    I like to think things are a bit cleaner now where:
    
    - `zdr_transcript` (if present) contains all messages in the history of
    the conversation, which includes function call outputs that have not
    been sent back to the model yet
    - `pending_input` includes any messages the user has submitted while the
    turn is in flight that need to be injected as part of the next `POST` to
    the model
    - `input_for_next_turn` includes the tool call outputs that have not
    been sent back to the model yet
  • chore: logging cleanup (#1196)
    Update what we log to make `RUST_LOG=debug` a bit easier to work with.
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/1196).
    * #1167
    * __->__ #1196
  • feat: show the version when starting Codex (#1182)
    The TypeScript version of the CLI shows the version when it starts up,
    which is helpful when users share screenshots (and nice to know, as a
    user).
  • feat: add hide_agent_reasoning config option (#1181)
    This PR introduces a `hide_agent_reasoning` config option (that defaults
    to `false`) that users can enable to make the output less verbose by
    suppressing reasoning output.
    
    To test, verified that this includes agent reasoning in the output:
    
    ```
    echo hello | just exec
    ```
    
    whereas this does not:
    
    ```
    echo hello | just exec --config hide_agent_reasoning=false
    ```
  • feat: dim the timestamp in the exec output (#1180)
    This required changing `ts_println!()` to take `$self:ident`, which is a
    bit more verbose, but the usability improvement seems worth it.
    
    Also eliminated an unnecessary `.to_string()` while here.
  • feat: grab-bag of improvements to exec output (#1179)
    Fixes:
    
    * Instantiate `EventProcessor` earlier in `lib.rs` so
    `print_config_summary()` can be an instance method of it and leverage
    its various `Style` fields to ensure it honors `with_ansi` properly.
    * After printing the config summary, print out user's prompt with the
    heading `User instructions:`. As noted in the comment, now that we can
    read the instructions via stdin as of #1178, it is helpful to the user
    to ensure they know what instructions were given to Codex.
    * Use same colors/bold/italic settings for headers as the TUI, making
    the output a bit easier to read.
  • feat: for codex exec, if PROMPT is not specified, read from stdin if not a TTY (#1178)
    This attempts to make `codex exec` more flexible in how the prompt can
    be passed:
    
    * as before, it can be passed as a single string argument
    * if `-` is passed as the value, the prompt is read from stdin
    * if no argument is passed _and stdin is a tty_, prints a warning to
    stderr that no prompt was specified an exits non-zero.
    * if no argument is passed _and stdin is NOT a tty_, prints `Reading
    prompt from stdin...` to stderr to let the user know that Codex will
    wait until it reads EOF from stdin to proceed. (You can repro this case
    by doing `yes | just exec` since stdin is not a TTY in that case but it
    also never reaches EOF).
  • fix: introduce create_tools_json() and share it with chat_completions.rs (#1177)
    The main motivator behind this PR is that `stream_chat_completions()`
    was not adding the `"tools"` entry to the payload posted to the
    `/chat/completions` endpoint. This (1) refactors the existing logic to
    build up the `"tools"` JSON from `client.rs` into `openai_tools.rs`, and
    (2) updates the use of responses API (`client.rs`) and chat completions
    API (`chat_completions.rs`) to both use it.
    
    Note this PR alone is not sufficient to get tool calling from chat
    completions working: that is done in
    https://github.com/openai/codex/pull/1167.
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/1177).
    * #1167
    * __->__ #1177
  • fix: enable set positional-arguments in justfile (#1169)
    The way these definitions worked before, they did not handle quoted args
    with spaces properly.
    
    For example, if you had `/tmp/test-just/printlen.py` as:
    
    ```python
    #!/usr/bin/env python3
    
    import sys
    
    print(len(sys.argv))
    ```
    
    and your `justfile` was:
    
    ```
    printlen *args:
        /tmp/test-just/printlen.py {{args}}
    ```
    
    Then:
    
    ```shell
    $ just printlen foo bar
    3
    $ just printlen 'foo bar'
    3
    ```
    
    which is not what we want: `'foo bar'` should be treated as one
    argument.
    
    The fix is to use
    [positional-arguments](https://github.com/casey/just/blob/515e806b5121a4696113ef15b5f0b12e69854570/README.md#L1131):
    
    ```
    set positional-arguments
    
    printlen *args:
        /tmp/test-just/printlen.py "$@"
    ```
  • docs: split the config-related portion of codex-rs/README.md into its own config.md file (#1165)
    Also updated the overview on `codex-rs/README.md` while here.
  • fix: introduce ResponseInputItem::McpToolCallOutput variant (#1151)
    The output of an MCP server tool call can be one of several types, but
    to date, we treated all outputs as text by showing the serialized JSON
    as the "tool output" in Codex:
    
    
    https://github.com/openai/codex/blob/25a9949c49194d5a64de54a11bcc5b4724ac9bd5/codex-rs/mcp-types/src/lib.rs#L96-L101
    
    This PR adds support for the `ImageContent` variant so we can now
    display an image output from an MCP tool call.
    
    In making this change, we introduce a new
    `ResponseInputItem::McpToolCallOutput` variant so that we can work with
    the `mcp_types::CallToolResult` directly when the function call is made
    to an MCP server.
    
    Though arguably the more significant change is the introduction of
    `HistoryCell::CompletedMcpToolCallWithImageOutput`, which is a cell that
    uses `ratatui_image` to render an image into the terminal. To support
    this, we introduce `ImageRenderCache`, cache a
    `ratatui_image::picker::Picker`, and `ensure_image_cache()` to cache the
    appropriate scaled image data and dimensions based on the current
    terminal size.
    
    To test, I created a minimal `package.json`:
    
    ```json
    {
      "name": "kitty-mcp",
      "version": "1.0.0",
      "type": "module",
      "description": "MCP that returns image of kitty",
      "main": "index.js",
      "dependencies": {
        "@modelcontextprotocol/sdk": "^1.12.0"
      }
    }
    ```
    
    with the following `index.js` to define the MCP server:
    
    ```js
    #!/usr/bin/env node
    
    import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
    import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
    import { readFile } from "node:fs/promises";
    import { join } from "node:path";
    
    const IMAGE_URI = "image://Ada.png";
    
    const server = new McpServer({
      name: "Demo",
      version: "1.0.0",
    });
    
    server.tool(
      "get-cat-image",
      "If you need a cat image, this tool will provide one.",
      async () => ({
        content: [
          { type: "image", data: await getAdaPngBase64(), mimeType: "image/png" },
        ],
      })
    );
    
    server.resource("Ada the Cat", IMAGE_URI, async (uri) => {
      const base64Image = await getAdaPngBase64();
      return {
        contents: [
          {
            uri: uri.href,
            mimeType: "image/png",
            blob: base64Image,
          },
        ],
      };
    });
    
    async function getAdaPngBase64() {
      const __dirname = new URL(".", import.meta.url).pathname;
      // From https://github.com/benjajaja/ratatui-image/blob/9705ce2c59ec669abbce2924cbfd1f5ae22c9860/assets/Ada.png
      const filePath = join(__dirname, "Ada.png");
      const imageData = await readFile(filePath);
      const base64Image = imageData.toString("base64");
      return base64Image;
    }
    
    const transport = new StdioServerTransport();
    await server.connect(transport);
    ```
    
    With the local changes from this PR, I added the following to my
    `config.toml`:
    
    ```toml
    [mcp_servers.kitty]
    command = "node"
    args = ["/Users/mbolin/code/kitty-mcp/index.js"]
    ```
    
    Running the TUI from source:
    
    ```
    cargo run --bin codex -- --model o3 'I need a picture of a cat'
    ```
    
    I get:
    
    <img width="732" alt="image"
    src="https://github.com/user-attachments/assets/bf80b721-9ca0-4d81-aec7-77d6899e2869"
    />
    
    Now, that said, I have only tested in iTerm and there is definitely some
    funny business with getting an accurate character-to-pixel ratio
    (sometimes the `CompletedMcpToolCallWithImageOutput` thinks it needs 10
    rows to render instead of 4), so there is still work to be done here.
  • fix: ensure inputSchema for MCP tool always has "properties" field when talking to OpenAI (#1150)
    As noted in the comment introduced in this PR, this is analogous to the
    issue reported in
    https://github.com/openai/openai-agents-python/issues/449. This seems to
    work now.
  • fix: honor RUST_LOG in mcp-client CLI and default to DEBUG (#1149)
    We had `debug!()` logging statements already, but they weren't being
    printed because `tracing_subscriber` was not set up.
  • feat: introduce CellWidget trait (#1148)
    The motivation behind this PR is to make it so a `HistoryCell` is more
    like a `WidgetRef` that knows how to render itself into a `Rect` so that
    it can be backed by something other than a `Vec<Line>`. Because a
    `HistoryCell` is intended to appear in a scrollable list, we want to
    ensure the stack of cells can be scrolled one `Line` at a time even if
    the `HistoryCell` is not backed by a `Vec<Line>` itself.
    
    To this end, we introduce the `CellWidget` trait whose key method is:
    
    ```
    fn render_window(&self, first_visible_line: usize, area: Rect, buf: &mut Buffer);
    ```
    
    The `first_visible_line` param is what differs from
    `WidgetRef::render_ref()`, as a `CellWidget` needs to know the offset
    into its "full view" at which it should start rendering.
    
    The bookkeeping in `ConversationHistoryWidget` has been updated
    accordingly to ensure each `CellWidget` in the history is rendered
    appropriately.
  • 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: TUI was not honoring --skip-git-repo-check correctly (#1105)
    I discovered that if I ran `codex <PROMPT>` in a cwd that was not a Git
    repo, Codex did not automatically run `<PROMPT>` after I accepted the
    Git warning. It appears that we were not managing the `AppState`
    transition correctly, so this fixes the bug and ensures the Codex
    session does not start until the user accepts the Git warning.
    
    In particular, we now create the `ChatWidget` lazily and store it in the
    `AppState::Chat` variant.
  • 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: introduce support for shell_environment_policy in config.toml (#1061)
    To date, when handling `shell` and `local_shell` tool calls, we were
    spawning new processes using the environment inherited from the Codex
    process itself. This means that the sensitive `OPENAI_API_KEY` that
    Codex needs to talk to OpenAI models was made available to everything
    run by `shell` and `local_shell`. While there are cases where that might
    be useful, it does not seem like a good default.
    
    This PR introduces a complex `shell_environment_policy` config option to
    control the `env` used with these tool calls. It is inevitably a bit
    complex so that it is possible to override individual components of the
    policy so without having to restate the entire thing.
    
    Details are in the updated `README.md` in this PR, but here is the
    relevant bit that explains the individual fields of
    `shell_environment_policy`:
    
    | Field | Type | Default | Description |
    | ------------------------- | -------------------------- | ------- |
    -----------------------------------------------------------------------------------------------------------------------------------------------
    |
    | `inherit` | string | `core` | Starting template for the
    environment:<br>`core` (`HOME`, `PATH`, `USER`, …), `all` (clone full
    parent env), or `none` (start empty). |
    | `ignore_default_excludes` | boolean | `false` | When `false`, Codex
    removes any var whose **name** contains `KEY`, `SECRET`, or `TOKEN`
    (case-insensitive) before other rules run. |
    | `exclude` | array&lt;string&gt; | `[]` | Case-insensitive glob
    patterns to drop after the default filter.<br>Examples: `"AWS_*"`,
    `"AZURE_*"`. |
    | `set` | table&lt;string,string&gt; | `{}` | Explicit key/value
    overrides or additions – always win over inherited values. |
    | `include_only` | array&lt;string&gt; | `[]` | If non-empty, a
    whitelist of patterns; only variables that match _one_ pattern survive
    the final step. (Generally used with `inherit = "all"`.) |
    
    
    In particular, note that the default is `inherit = "core"`, so:
    
    * if you have extra env variables that you want to inherit from the
    parent process, use `inherit = "all"` and then specify `include_only`
    * if you have extra env variables where you want to hardcode the values,
    the default `inherit = "core"` will work fine, but then you need to
    specify `set`
    
    This configuration is not battle-tested, so we will probably still have
    to play with it a bit. `core/src/exec_env.rs` has the critical business
    logic as well as unit tests.
    
    Though if nothing else, previous to this change:
    
    ```
    $ cargo run --bin codex -- debug seatbelt -- printenv OPENAI_API_KEY
    # ...prints OPENAI_API_KEY...
    ```
    
    But after this change it does not print anything (as desired).
    
    One final thing to call out about this PR is that the
    `configure_command!` macro we use in `core/src/exec.rs` has to do some
    complex logic with respect to how it builds up the `env` for the process
    being spawned under Landlock/seccomp. Specifically, doing
    `cmd.env_clear()` followed by `cmd.envs(&$env_map)` (which is arguably
    the most intuitive way to do it) caused the Landlock unit tests to fail
    because the processes spawned by the unit tests started failing in
    unexpected ways! If we forgo `env_clear()` in favor of updating env vars
    one at a time, the tests still pass. The comment in the code talks about
    this a bit, and while I would like to investigate this more, I need to
    move on for the moment, but I do plan to come back to it to fully
    understand what is going on. For example, this suggests that we might
    not be able to spawn a C program that calls `env_clear()`, which would
    be...weird. We may still have to fiddle with our Landlock config if that
    is the case.
  • feat: show Config overview at start of exec (#1073)
    Now the `exec` output starts with something like:
    
    ```
    --------
    workdir:  /Users/mbolin/code/codex/codex-rs
    model:  o3
    provider:  openai
    approval:  Never
    sandbox:  SandboxPolicy { permissions: [DiskFullReadAccess, DiskWritePlatformUserTempFolder, DiskWritePlatformGlobalTempFolder, DiskWriteCwd, DiskWriteFolder { folder: "/Users/mbolin/.pyenv/shims" }] }
    --------
    ```
    
    which makes it easier to reason about when looking at logs.
  • chore: move types out of config.rs into config_types.rs (#1054)
    `config.rs` is already quite long without these definitions. Since they
    have no real dependencies of their own, let's move them to their own
    file so `config.rs` can focus on the business logic of loading a config.
  • 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.
  • fix: artifacts from previous frames were bleeding through in TUI (#989)
    Prior to this PR, I would frequently see glyphs from previous frames
    "bleed" through like this:
    
    
    ![image](https://github.com/user-attachments/assets/8784b3d7-f691-4df6-8666-34e2f134ee85)
    
    I think this was due to two issues (now addressed in this PR):
    
    * We were not making use of `ratatui::widgets::Clear` to clear out the
    buffer before drawing into it.
    * To calculate the `width` used with `wrapped_line_count_for_cell()`, we
    were not accounting for the scrollbar.
    * Now we calculate `effective_width` using
    `inner.width.saturating_sub(1)` where the `1` is for the scrollbar.
    * We compute `text_area` using `effective_with` and pass the `text_area`
    to `paragraph.render()`.
    * We eliminate the conditional `needs_scrollbar` check and always call
    `render(Scrollbar)`
    
    I suspect this bug was introduced in
    https://github.com/openai/codex/pull/937, though I did not try to
    verify: I'm just happy that it appears to be fixed!
  • fix: ensure the first user message always displays after the session info (#988)
    Previously, if the first user message was sent with the command
    invocation, e.g.:
    
    ```
    $ cargo run --bin codex 'hello'
    ```
    
    Then the user message was added as the first entry in the history and
    then `is_first_event` would be `false` here:
    
    
    https://github.com/openai/codex/blob/031df77dfb9248fe47b40ec52bf8b05052231722/codex-rs/tui/src/conversation_history_widget.rs#L178-L179
    
    which would prevent the "welcome" message with things like the the model
    version from displaying.
    
    The fix in this PR is twofold:
    
    * Reorganize the logic so the `ChatWidget` constructor stores
    `initial_user_message` rather than sending it right away. Now inside
    `handle_codex_event()`, it waits for the `SessionConfigured` event and
    sends the `initial_user_message`, if it exists.
    * In `conversation_history_widget.rs`, `add_session_info()` checks to
    see whether a `WelcomeMessage` exists in the history when determining
    the value of `has_welcome_message`. By construction, we expect that
    `WelcomeMessage` is always the first message (in which case the existing
    `let is_first_event = self.entries.is_empty();` logic would be sound),
    but we decide to be extra defensive in case an `EventMsg::Error` is
    processed before `EventMsg::SessionConfigured`.
  • fix: make codex-mini-latest the default model in the Rust TUI (#972)
    It's time to make `codex-mini-latest` the new default, as this should be
    an "evergreen" model pointer.
    
    * Equivalent change in TypeScript
    https://github.com/openai/codex/pull/951
    * See some notes about using `codex-mini-latest` with MCP in
    https://github.com/openai/codex/pull/961
  • feat: make it possible to toggle mouse mode in the Rust TUI (#971)
    I did a bit of research to understand why I could not use my mouse to
    drag to select text to copy to the clipboard in iTerm.
    
    Apparently https://github.com/openai/codex/pull/641 to enable mousewheel
    scrolling broke this functionality. It seems that, unless we put in a
    bit of effort, we can have drag-to-select or scrolling, but not both.
    Though if you know the trick to hold down `Option` will dragging with
    the mouse in iTerm, you can probably get by with this. (I did not know
    about this option prior to researching this issue.)
    
    Nevertheless, users may still prefer to disable mouse capture
    altogether, so this PR introduces:
    
    * the ability to set `tui.disable_mouse_capture = true` in `config.toml`
    to disable mouse capture
    * a new command, `/toggle-mouse-mode` to toggle mouse capture
  • fix: use text other than 'TODO' as test example (#969)
    I casually `rg TODO` to look for TODOs, so the use of TODO in a sample
    string in test output was throwing things off.
  • feat: add support for OpenAI tool type, local_shell (#961)
    The new `codex-mini-latest` model expects a new tool with `{"type":
    "local_shell"}`. Its contract is similar to the existing `function` tool
    with `"name": "shell"`, so this takes the `local_shell` tool call into
    `ExecParams` and sends it through the existing
    `handle_container_exec_with_params()` code path.
    
    This also adds the following logic when adding the default set of tools
    to a request:
    
    ```rust
    let default_tools = if self.model.starts_with("codex") {
        &DEFAULT_CODEX_MODEL_TOOLS
    } else {
        &DEFAULT_TOOLS
    };
    ```
    
    That is, if the model name starts with `"codex"`, we add `{"type":
    "local_shell"}` to the list of tools; otherwise, we add the
    aforementioned `shell` tool.
    
    To test this, I ran the TUI with `-m codex-mini-latest` and verified
    that it used the `local_shell` tool. Though I also had some entries in
    `[mcp_servers]` in my personal `config.toml`. The `codex-mini-latest`
    model seemed eager to try the tools from the MCP servers first, so I
    have personally commented them out for now, so keep an eye out if you're
    testing `codex-mini-latest`!
    
    Perhaps we should include more details with `{"type": "local_shell"}` or
    update the following:
    
    
    https://github.com/openai/codex/blob/fd0b1b020818dfe8aaf7eb68425f09e86ab1b819/codex-rs/core/prompt.md
    
    For reference, the corresponding change in the TypeScript CLI is
    https://github.com/openai/codex/pull/951.
  • chore: refactor handle_function_call() into smaller functions (#965)
    Overall, `codex.rs` is still far too large, but at least there's less
    indenting now that things have been moved into smaller functions.
    
    This will also make it easier to introduce the `local_shell` tool in
    https://github.com/openai/codex/pull/961.
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/965).
    * #961
    * __->__ #965
  • feat: add support for file_opener option in Rust, similiar to #911 (#957)
    This ports the enhancement introduced in
    https://github.com/openai/codex/pull/911 (and the fixes in
    https://github.com/openai/codex/pull/919) for the TypeScript CLI to the
    Rust one.
  • fix: apply patch issue when using different cwd (#942)
    If you run a codex instance outside of the current working directory
    from where you launched the codex binary it won't be able to apply
    patches correctly, even if the sandbox policy allows it. This manifests
    weird behaviours, such as
    
    * Reading the same filename in the binary working directory, and
    overwriting it in the session working directory. e.g. if you have a
    `readme` in both folders it will overwrite the readme in the session
    working directory with the readme in the binary working directory
    *applied with the suggested patch*.
    * The LLM ends up in weird loops trying to verify and debug why the
    apply_patch won't work, and it can result in it applying patches by
    manually writing python or javascript if it figures out that either is
    supported by the system instead.
    
    I added a test-case to ensure that the patch contents are based on the
    cwd.
    
    ## Issue: mixing relative & absolute paths in apply_patch
    
    1. The apply_patch tool use relative paths based on the session working
    directory.
    2. `unified_diff_from_chunks` eventually ends up [reading the source
    file](https://github.com/reflectionai/codex/blob/main/codex-rs/apply-patch/src/lib.rs#L410)
    to figure out what the diff is, by using the relative path.
    3. The changes are targeted using an absolute path derived from the
    current working directory.
    
    The end-result in case session working directory differs from the binary
    working directory: we get the diff for a file relative to the binary
    working directory, and apply it on a file in the session working
    directory.
  • chore: update exec crate to use std::time instead of chrono (#952)
    When I originally wrote `elapsed.rs`, I realized we were using both
    `std::time` and `chrono` with no real benefit of having both. We should
    try to keep the `exec` subcommand trim (as it also buildable as a
    standalone executable), so this helps tighten things up.
  • 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: introduce AppEventSender to help fix clippy warnings and update to Rust 1.87 (#948)
    Moving to Rust 1.87 introduced a clippy warning that
    `SendError<AppEvent>` was too large.
    
    In practice, the only thing we ever did when we got this error was log
    it (if the mspc channel is closed, then the app is likely shutting down
    or something, so there's not much to do...), so this finally motivated
    me to introduce `AppEventSender`, which wraps
    `std::sync::mpsc::Sender<AppEvent>` with a `send()` method that invokes
    `send()` on the underlying `Sender` and logs an `Err` if it gets one.
    
    This greatly simplifies the code, as many functions that previously
    returned `Result<(), SendError<AppEvent>>` now return `()`, so we don't
    have to propagate an `Err` all over the place that we don't really
    handle, anyway.
    
    This also makes it so we can upgrade to Rust 1.87 in CI.
  • chore: pin Rust version to 1.86 and use io::Error::other to prepare for 1.87 (#947)
    Previously, our GitHub actions specified the Rust toolchain as
    `dtolnay/rust-toolchain@stable`, which meant the version could change
    out from under us. In this case, the move from 1.86 to 1.87 introduced
    new clippy warnings, causing build failures.
    
    Because it will take a little time to fix all the new clippy warnings,
    this PR pins things to 1.86 for now to unbreak the build.
    
    It also replaces `io::Error::new(io::ErrorKind::Other)` with
    `io::Error::other()` in preparation for 1.87.
  • fix: properly wrap lines in the Rust TUI (#937)
    As discussed on
    https://github.com/openai/codex/commit/699ec5a87f09796d17c0202cd92a1dd4d8b4f3f5#commitcomment-156776835,
    to properly support scrolling long content in Ratatui for a sequence of
    cells, we need to:
    
    * take the `Vec<Line>` for each cell
    * using the wrapping logic we want to use at render time, compute the
    _effective line count_ using `Paragraph::line_count()` (see
    `wrapped_line_count_for_cell()` in this PR)
    * sum up the effective line count to compute the height of the area
    being scrolled
    * given a `scroll_position: usize`, index into the list of "effective
    lines" and accumulate the appropriate `Vec<Line>` for the cells that
    should be displayed
    * take that `Vec<Line>` to create a `Paragraph` and use the same
    line-wrapping policy that was used in `wrapped_line_count_for_cell()`
    * display the resulting `Paragraph` and use the accounting to display a
    scrollbar with the appropriate thumb size and offset without having to
    render the `Vec<Line>` for the full history
    
    With this change, lines wrap as I expect and everything appears to
    redraw correctly as I resize my terminal!
  • 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.