Commit Graph

1111 Commits

  • chore: improve docstring for --full-auto (#1379)
    Reference `-c sandbox.mode=workspace-write` in the docstring and users
    can read the config docs for `sandbox` for more information.
  • fix: pretty-print the sandbox config in the TUI/exec modes (#1376)
    Now that https://github.com/openai/codex/pull/1373 simplified the
    sandbox config, we can print something much simpler in the TUI (and in
    `codex exec`) to summarize the sandbox config.
    
    Before:
    
    ![Screenshot 2025-06-24 at 5 45
    52 PM](https://github.com/user-attachments/assets/b7633efb-a619-43e1-9abe-7bb0be2d0ec0)
    
    With this change:
    
    ![Screenshot 2025-06-24 at 5 46
    44 PM](https://github.com/user-attachments/assets/8d099bdd-a429-4796-a08d-70931d984e4f)
    
    For reference, my `config.toml` contains:
    
    ```
    [sandbox]
    mode = "workspace-write"
    writable_roots = ["/tmp", "/Users/mbolin/.pyenv/shims"]
    ```
    
    Fixes https://github.com/openai/codex/issues/1248
  • 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.
  • codex-rs: Rename /clear to /new, make it start an entirely new chat (#1264)
    I noticed that `/clear` wasn't fully clearing chat history; it would
    clear the chat history widgets _in the UI_, but the LLM still had access
    to information from previous messages.
    
    This PR renames `/clear` to `/new` for clarity as per Michael's
    suggestion, resetting `app_state` to a fresh `ChatWidget`.
  • feat: add support for login with ChatGPT (#1212)
    This does not implement the full Login with ChatGPT experience, but it
    should unblock people.
    
    **What works**
    
    * The `codex` multitool now has a `login` subcommand, so you can run
    `codex login`, which should write `CODEX_HOME/auth.json` if you complete
    the flow successfully. The TUI will now read the `OPENAI_API_KEY` from
    `auth.json`.
    * The TUI should refresh the token if it has expired and the necessary
    information is in `auth.json`.
    * There is a `LoginScreen` in the TUI that tells you to run `codex
    login` if both (1) your model provider expects to use `OPENAI_API_KEY`
    as its env var, and (2) `OPENAI_API_KEY` is not set.
    
    **What does not work**
    
    * The `LoginScreen` does not support the login flow from within the TUI.
    Instead, it tells you to quit, run `codex login`, and then run `codex`
    again.
    * `codex exec` does read from `auth.json` yet, nor does it direct the
    user to go through the login flow if `OPENAI_API_KEY` is not be found.
    * The `maybeRedeemCredits()` function from `get-api-key.tsx` has not
    been ported from TypeScript to `login_with_chatgpt.py` yet:
    
    
    https://github.com/openai/codex/blob/a67a67f3258fc21e147b6786a143fe3e15e6d5ba/codex-cli/src/utils/get-api-key.tsx#L84-L89
    
    **Implementation**
    
    Currently, the OAuth flow requires running a local webserver on
    `127.0.0.1:1455`. It seemed wasteful to incur the additional binary cost
    of a webserver dependency in the Rust CLI just to support login, so
    instead we implement this logic in Python, as Python has a `http.server`
    module as part of its standard library. Specifically, we bundle the
    contents of a single Python file as a string in the Rust CLI and then
    use it to spawn a subprocess as `python3 -c
    {{SOURCE_FOR_PYTHON_SERVER}}`.
    
    As such, the most significant files in this PR are:
    
    ```
    codex-rs/login/src/login_with_chatgpt.py
    codex-rs/login/src/lib.rs
    ```
    
    Now that the CLI may load `OPENAI_API_KEY` from the environment _or_
    `CODEX_HOME/auth.json`, we need a new abstraction for reading/writing
    this variable, so we introduce:
    
    ```
    codex-rs/core/src/openai_api_key.rs
    ```
    
    Note that `std::env::set_var()` is [rightfully] `unsafe` in Rust 2024,
    so we use a LazyLock<RwLock<Option<String>>> to store `OPENAI_API_KEY`
    so it is read in a thread-safe manner.
    
    Ultimately, it should be possible to go through the entire login flow
    from the TUI. This PR introduces a placeholder `LoginScreen` UI for that
    right now, though the new `codex login` subcommand introduced in this PR
    should be a viable workaround until the UI is ready.
    
    **Testing**
    
    Because the login flow is currently implemented in a standalone Python
    file, you can test it without building any Rust code as follows:
    
    ```
    rm -rf /tmp/codex_home && mkdir /tmp/codex_home
    CODEX_HOME=/tmp/codex_home python3 codex-rs/login/src/login_with_chatgpt.py
    ```
    
    For reference:
    
    * the original TypeScript implementation was introduced in
    https://github.com/openai/codex/pull/963
    * support for redeeming credits was later added in
    https://github.com/openai/codex/pull/974
  • 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.
  • 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.
  • 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
    ```
  • 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.
  • 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.
  • 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`.
  • 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 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.
  • 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.
  • 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!
  • 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/`.
  • feat: add support for commands in the Rust TUI (#935)
    Introduces support for slash commands like in the TypeScript CLI. We do
    not support the full set of commands yet, but the core abstraction is
    there now.
    
    In particular, we have a `SlashCommand` enum and due to thoughtful use
    of the [strum](https://crates.io/crates/strum) crate, it requires
    minimal boilerplate to add a new command to the list.
    
    The key new piece of UI is `CommandPopup`, though the keyboard events
    are still handled by `ChatComposer`. The behavior is roughly as follows:
    
    * if the first character in the composer is `/`, the command popup is
    displayed (if you really want to send a message to Codex that starts
    with a `/`, simply put a space before the `/`)
    * while the popup is displayed, up/down can be used to change the
    selection of the popup
    * if there is a selection, hitting tab completes the command, but does
    not send it
    * if there is a selection, hitting enter sends the command
    * if the prefix of the composer matches a command, the command will be
    visible in the popup so the user can see the description (commands could
    take arguments, so additional text may appear after the command name
    itself)
    
    
    https://github.com/user-attachments/assets/39c3e6ee-eeb7-4ef7-a911-466d8184975f
    
    Incidentally, Codex wrote almost all the code for this PR!
  • chore: move each view used in BottomPane into its own file (#928)
    `BottomPane` was getting a bit unwieldy because it maintained a
    `PaneState` enum with three variants and many of its methods had `match`
    statements to handle each variant. To replace the enum, this PR:
    
    * Introduces a `trait BottomPaneView` that has two implementations:
    `StatusIndicatorView` and `ApprovalModalView`.
    * Migrates `PaneState::TextInput` into its own struct, `ChatComposer`,
    that does **not** implement `BottomPaneView`.
    * Updates `BottomPane` so it has `composer: ChatComposer` and
    `active_view: Option<Box<dyn BottomPaneView<'a> + 'a>>`. The idea is
    that `active_view` takes priority and is displayed when it is `Some`;
    otherwise, `ChatComposer` is displayed.
    * While methods of `BottomPane` often have to check whether
    `active_view` is present to decide which component to delegate to, the
    code is more straightforward than before and introducing new
    implementations of `BottomPaneView` should be less painful.
    
    Because we want to retain the `TextArea` owned by `ChatComposer` even
    when another view is displayed, to keep the ownership logic simple, it
    seemed best to keep `ChatComposer` distinct from `BottomPaneView`.
  • feat: Ctrl+J for newline in Rust TUI, default to one line of height (#926)
    While the `TextArea` used in the Rust TUI is "multiline," it is not like
    an HTML `<textarea>` in that it does not wrap, so there was not much
    benefit to setting `MIN_TEXTAREA_ROWS` to `3`, so this PR changes it to
    `1`. Though there are now three ways to "increase" the height due to
    actual linebreaks:
    
    * paste in multiline content (this worked before this PR)
    * pressing `Ctrl+J` will insert a newline
    * if you have your terminal emulator set such that it is possible to
    press something that `crossterm` interprets as "Enter plus some
    modifier," then now that will also work
    
    Now things look a bit more compact on startup:
    
    <img width="745" alt="image"
    src="https://github.com/user-attachments/assets/86e2857f-f31c-46f5-a80b-1ab2120b266e"
    />
  • 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.
  • fix: tighten up some logic around session timestamps and ids (#922)
    * update `SessionConfigured` event to include the UUID for the session
    * show the UUID in the Rust TUI
    * use local timestamps in log files instead of UTC
    * include timestamps in log file names for easier discovery
  • 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`.
  • fix: fix border style for BottomPane (#893)
    This PR fixes things so that:
    
    * when the `BottomPane` is in the `StatusIndicator` state, the border
    should be dim
    * when the `BottomPane` does not have input focus, the border should be
    dim
    
    To make it easier to enforce this invariant, this PR introduces
    `BottomPane::set_state()` that will:
    
    * update `self.state`
    * call `update_border_for_input_focus()`
    * request a repaint
    
    This should make it easier to enforce other updates for state changes
    going forward.
  • feat: include "reasoning" messages in Rust TUI (#892)
    As shown in the screenshot, we now include reasoning messages from the
    model in the TUI under the heading "codex reasoning":
    
    
    ![image](https://github.com/user-attachments/assets/d8eb3dc3-2f9f-4e95-847e-d24b421249a8)
    
    To ensure these are visible by default when using `o4-mini`, this also
    changes the default value for `summary` (formerly `generate_summary`,
    which is deprecated in favor of `summary` according to the docs) from
    unset to `"auto"`.
  • feat: Allow pasting newlines (#866)
    Noticed that when pasting multi-line blocks, each newline was treated
    like a new submission.
    Update tui to handle Paste directly and map newlines to shift+enter.
    
    # Test
    
    Copied this into clipboard:
    ```
    Do nothing.
    Explain this repo to me.
    ```
    
    Pasted in and saw multi-line input. Hitting Enter then submitted the
    full block.
  • 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: 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!
  • 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 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: 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.
  • 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)
  • 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.