Commit Graph
62 Commits
Author SHA1 Message Date
Celia ChenandGitHub 9eef2e91fc fix: allow restricted filesystem profiles to read helper executables (#15114)
## Summary

This PR fixes restricted filesystem permission profiles so Codex's
runtime-managed helper executables remain readable without requiring
explicit user configuration.

- add implicit readable roots for the configured `zsh` helper path and
the main execve wrapper
- allowlist the shared `$CODEX_HOME/tmp/arg0` root when the execve
wrapper lives there, so session-specific helper paths keep working
- dedupe injected paths and avoid adding duplicate read entries to the
sandbox policy
- add regression coverage for restricted read mode with helper
executable overrides

## Testing 
before this change: got this error when executing a shell command via
zsh fork:
```
"sandbox error: sandbox denied exec error, exit code: 127, stdout: , stderr: /etc/zprofile:11: operation not permitted: /usr/libexec/path_helper\nzsh:1: operation not permitted: .codex/skills/proxy-a/scripts/fetch_example.sh\n"
```

saw this change went away after this change, meaning the readable roots
and injected correctly.
2026-03-20 15:51:06 -07:00
Celia ChenandGitHub 0c60eea4a5 feat: support skill-scoped managed network domain overrides in skill config (#14522)
## Summary

This lets skill loading split `permissions.network` into two distinct
pieces:

- `permissions.network.enabled` still feeds the skill
`PermissionProfile` and remains the coarse gate for whether the skill
can use network access at all.
- `permissions.network.allowed_domains` and
`permissions.network.denied_domains` are lifted into a new
`SkillManagedNetworkOverride` so managed-network sessions can start
per-skill scoped proxies with the right domain overrides.

The change also updates `SkillMetadata` construction sites and adds
loader tests covering YAML parsing plus normalization of the network
gate vs. domain override fields.

## Follow-up
A PR that uses the network_override to spin up a skill-specific proxy if
network_override is not none.
2026-03-13 04:45:14 +00:00
Celia ChenandMichael Bolin c1a424691f chore: add a separate reject-policy flag for skill approvals (#14271)
## Summary
- add `skill_approval` to `RejectConfig` and the app-server v2
`AskForApproval::Reject` payload so skill-script prompts can be
configured independently from sandbox and rule-based prompts
- update Unix shell escalation to reject prompts based on the actual
decision source, keeping prefix rules tied to `rules`, unmatched command
fallbacks tied to `sandbox_approval`, and skill scripts tied to
`skill_approval`
- regenerate the affected protocol/config schemas and expand
unit/integration coverage for the new flag and skill approval behavior
2026-03-11 12:33:09 -07:00
Celia ChenandGitHub 340f9c9ecb app-server: include experimental skill metadata in exec approval requests (#13929)
## Summary

This change surfaces skill metadata on command approval requests so
app-server clients can tell when an approval came from a skill script
and identify the originating `SKILL.md`.

- add `skill_metadata` to exec approval events in the shared protocol
- thread skill metadata through core shell escalation and delegated
approval handling for skill-triggered approvals
- expose the field in app-server v2 as experimental `skillMetadata`
- regenerate the JSON/TypeScript schemas and cover the new field in
protocol, transport, core, and TUI tests

## Why

Skill-triggered approvals already carry skill context inside core, but
app-server clients could not see which skill caused the prompt. Sending
the skill metadata with the approval request makes it possible for
clients to present better approval UX and connect the prompt back to the
relevant skill definition.


## example event in app-server-v2
verified that we see this event when experimental api is on:
```
< {
<   "id": 11,
<   "method": "item/commandExecution/requestApproval",
<   "params": {
<     "additionalPermissions": {
<       "fileSystem": null,
<       "macos": {
<         "accessibility": false,
<         "automations": {
<           "bundle_ids": [
<             "com.apple.Notes"
<           ]
<         },
<         "calendar": false,
<         "preferences": "read_only"
<       },
<       "network": null
<     },
<     "approvalId": "25d600ee-5a3c-4746-8d17-e2e61fb4c563",
<     "availableDecisions": [
<       "accept",
<       "acceptForSession",
<       "cancel"
<     ],
<     "command": "/Applications/ChatGPT.app/Contents/Resources/CodexAppServer_CodexAppServerBundledSkills.bundle/Contents/Resources/skills/apple-notes/scripts/notes_info",
<     "commandActions": [
<       {
<         "command": "/Applications/ChatGPT.app/Contents/Resources/CodexAppServer_CodexAppServerBundledSkills.bundle/Contents/Resources/skills/apple-notes/scripts/notes_info",
<         "type": "unknown"
<       }
<     ],
<     "cwd": "/Applications/ChatGPT.app/Contents/Resources/CodexAppServer_CodexAppServerBundledSkills.bundle/Contents/Resources/skills/apple-notes",
<     "itemId": "call_jZp3xFpNg4D8iKAD49cvEvZy",
<     "skillMetadata": {
<       "pathToSkillsMd": "/Applications/ChatGPT.app/Contents/Resources/CodexAppServer_CodexAppServerBundledSkills.bundle/Contents/Resources/skills/apple-notes/SKILL.md"
<     },
<     "threadId": "019ccc10-b7d3-7ff2-84fe-3a75e7681e69",
<     "turnId": "019ccc10-b848-76f1-81b3-4a1fa225493f"
<   }
< }`
```

& verified that this is the event when experimental api is off:
```
< {
<   "id": 13,
<   "method": "item/commandExecution/requestApproval",
<   "params": {
<     "approvalId": "5fbbf776-261b-4cf8-899b-c125b547f2c0",
<     "availableDecisions": [
<       "accept",
<       "acceptForSession",
<       "cancel"
<     ],
<     "command": "/Applications/ChatGPT.app/Contents/Resources/CodexAppServer_CodexAppServerBundledSkills.bundle/Contents/Resources/skills/apple-notes/scripts/notes_info",
<     "commandActions": [
<       {
<         "command": "/Applications/ChatGPT.app/Contents/Resources/CodexAppServer_CodexAppServerBundledSkills.bundle/Contents/Resources/skills/apple-notes/scripts/notes_info",
<         "type": "unknown"
<       }
<     ],
<     "cwd": "/Users/celia/code/codex/codex-rs",
<     "itemId": "call_OV2DHzTgYcbYtWaTTBWlocOt",
<     "threadId": "019ccc16-2a2b-7be1-8500-e00d45b892d4",
<     "turnId": "019ccc16-2a8e-7961-98ec-649600e7d06a"
<   }
< }
```
2026-03-08 18:07:46 -07:00
Celia ChenandGitHub b0ce16c47a fix(core): respect reject policy by approval source for skill scripts (#13816)
## Summary
- distinguish reject-policy handling for prefix-rule approvals versus
sandbox approvals in Unix shell escalation
- keep prompting for skill-script execution when `rules=true` but
`sandbox_approval=false`, instead of denying the command up front
- add regression coverage for both skill-script reject-policy paths in
`codex-rs/core/tests/suite/skill_approval.rs`
2026-03-06 21:43:14 -08:00
Celia ChenandGitHub 8b81284975 fix(core): skip exec approval for permissionless skill scripts (#13791)
## Summary

- Treat skill scripts with no permission profile, or an explicitly empty
one, as permissionless and run them with the turn's existing sandbox
instead of forcing an exec approval prompt.
- Keep the approval flow unchanged for skills that do declare additional
permissions.
- Update the skill approval tests to assert that permissionless skill
scripts do not prompt on either the initial run or a rerun.

## Why

Permissionless skills should inherit the current turn sandbox directly.
Prompting for exec approval in that case adds friction without granting
any additional capability.
2026-03-06 16:40:41 -08:00
Celia ChenandGitHub f9ce403b5a fix: accept two macOS automation input shapes for approval payload compatibility (#13683)
## Summary
This PR:
1. fixes a deserialization mismatch for macOS automation permissions in
approval payloads by making core parsing accept both supported wire
shapes for bundle IDs.
2. added `#[serde(default)]` to `MacOsSeatbeltProfileExtensions` so
omitted fields deserialize to secure defaults.


## Why this change is needed
`MacOsAutomationPermission` uses `#[serde(try_from =
"MacOsAutomationPermissionDe")]`, so deserialization is controlled by
`MacOsAutomationPermissionDe`. After we aligned v2
`additionalPermissions.macos.automations` to the core shape, approval
payloads started including `{ "bundle_ids": [...] }` in some paths.
`MacOsAutomationPermissionDe` previously accepted only `"none" | "all"`
or a plain array, so object-shaped bundle IDs failed with `data did not
match any variant of untagged enum MacOsAutomationPermissionDe`. This
change restores compatibility by accepting both forms while preserving
existing normalization behavior (trim values and map empty bundle lists
to `None`).

## Validation

saw this error went away when running
```
cargo run -p codex-app-server-test-client -- \
    --codex-bin ./target/debug/codex \
    -c 'approval_policy="on-request"' \
    -c 'features.shell_zsh_fork=true' \
    -c 'zsh_path="/tmp/codex-zsh-fork/package/vendor/aarch64-apple-darwin/zsh/macos-15/zsh"' \
    send-message-v2 --experimental-api \
    'Use $apple-notes and run scripts/notes_info now.'
```
:
```
Error: failed to deserialize ServerRequest from JSONRPCRequest

Caused by:
    data did not match any variant of untagged enum MacOsAutomationPermissionDe
```
2026-03-06 06:02:33 +00:00
Celia ChenandGitHub fb9fcf060f chore: remove unused legacy macOS permission types (#13677)
## Summary

This PR removes legacy macOS permission model types from
`codex-rs/protocol/src/models.rs`:

- `MacOsPermissions`
- `MacOsPreferencesValue`
- `MacOsAutomationValue`

The protocol now relies on the current `MacOsSeatbeltProfileExtensions`
model for macOS permission data.
2026-03-06 05:32:40 +00:00
Celia ChenandGitHub aaefee04cd core/protocol: add structured macOS additional permissions and merge them into sandbox execution (#13499)
## Summary
- Introduce strongly-typed macOS additional permissions across
protocol/core/app-server boundaries.
- Merge additional permissions into effective sandbox execution,
including macOS seatbelt profile extensions.
- Expand docs, schema/tool definitions, UI rendering, and tests for
`network`, `file_system`, and `macos` additional permissions.
2026-03-05 16:21:45 -08:00
Celia ChenandGitHub a63624a61a feat: merge skill permission profiles into the turn sandbox for zsh-fork execs (#13496)
## Summary

This changes the Unix shell escalation path for skill-matched
executables to apply a skill's `PermissionProfile` as additive
permissions on top of the existing turn/request sandbox policy.

Previously, skill-matched executables compiled the skill permission
profile into a standalone sandbox policy and executed against that
replacement policy. Now they go through the same
`additional_permissions` merge path used elsewhere in shell sandbox
preparation.

## What Changed

- Changed `skill_escalation_execution()` to return
`EscalationPermissions::PermissionProfile(...)` for non-empty skill
permission profiles.
- Kept empty or missing skill permission profiles on the `TurnDefault`
path.
- Added tests covering the new additive skill-permission behavior.
- Added inline comments in `prepare_escalated_exec()` clarifying the
difference between additive permission merging and fully specified
replacement sandbox policies.
- Removed the now-unused skill permission compiler module after
switching this path away from standalone compiled skill sandbox
policies.

## Testing

- Ran `just fmt` in `codex-rs`
- Ran `cargo test -p codex-core`

`cargo test -p codex-core` still hits an unrelated existing failure:
`shell_snapshot::tests::snapshot_shell_does_not_inherit_stdin`

## Follow-up

This change intentionally does not merge skill-specific macOS seatbelt
profile extensions through the `additional_permissions` path yet.
Filesystem and network permissions now follow the additive merge path,
but seatbelt extension permissions still need separate handling in a
follow-up PR.
2026-03-05 20:05:35 +00:00
Celia ChenandGitHub d622bff384 chore: Nest skill and protocol network permissions under network.enabled (#13427)
## Summary

Changes the permission profile shape from a bare network boolean to a
nested object.

Before:

```yaml
permissions:
  network: true
```

After:

```yaml
permissions:
  network:
    enabled: true
```

This also updates the shared Rust and app-server protocol types so
`PermissionProfile.network` is no longer `Option<bool>`, but
`Option<NetworkPermissions>` with `enabled: Option<bool>`.

## What Changed

- Updated `PermissionProfile` in `codex-rs/protocol/src/models.rs`:
- `pub network: Option<bool>` -> `pub network:
Option<NetworkPermissions>`
- Added `NetworkPermissions` with:
  - `pub enabled: Option<bool>`
- Changed emptiness semantics so `network` is only considered empty when
`enabled` is `None`
- Updated skill metadata parsing to accept `permissions.network.enabled`
- Updated core permission consumers to read
`network.enabled.unwrap_or(false)` where a concrete boolean is needed
- Updated app-server v2 protocol types and regenerated schema/TypeScript
outputs
- Updated docs to mention `additionalPermissions.network.enabled`
2026-03-03 20:57:29 -08:00
Celia ChenandGitHub e6773f856c Feat: Preserve network access on read-only sandbox policies (#13409)
## Summary

`PermissionProfile.network` could not be preserved when additional or
compiled permissions resolved to
`SandboxPolicy::ReadOnly`, because `ReadOnly` had no network_access
field. This change makes read-only + network
enabled representable directly and threads that through the protocol,
app-server v2 mirror, and permission-
  merging logic.

## What changed

- Added `network_access: bool` to `SandboxPolicy::ReadOnly` in the core
protocol and app-server v2 protocol.
- Kept backward compatibility by defaulting the new field to false, so
legacy read-only payloads still
    deserialize unchanged.
- Updated `has_full_network_access()` and sandbox summaries to respect
read-only network access.
  - Preserved PermissionProfile.network when:
      - compiling skill permission profiles into sandbox policies
      - normalizing additional permissions
      - merging additional permissions into existing sandbox policies
- Updated the approval overlay to show network in the rendered
permission rule when requested.
  - Regenerated app-server schema fixtures for the new v2 wire shape.
2026-03-04 02:41:57 +00:00
Celia ChenandGitHub 0bb152b01d chore: remove SkillMetadata.permissions and derive skill sandboxing from permission_profile (#13061)
## Summary

This change removes the compiled permissions field from skill metadata
and keeps permission_profile as the single source of truth.

Skill loading no longer compiles skill permissions eagerly. Instead, the
zsh-fork skill escalation path compiles `skill.permission_profile` when
it needs to determine the sandbox to apply for a skill script.

  ## Behavior change

  For skills that declare:
```
  permissions: {}
```
we now treat that the same as having no skill permissions override,
instead of creating and using a default readonly sandbox. This change
makes the behavior more intuitive:

  - only non-empty skill permission profiles affect sandboxing
- omitting permissions and writing permissions: {} now mean the same
thing
- skill metadata keeps a single permissions representation instead of
storing derived state too

Overall, this makes skill sandbox behavior easier to understand and more
predictable.
2026-03-03 01:29:53 +00:00
Celia ChenandGitHub 90cc4e79a2 feat: add local date/timezone to turn environment context (#12947)
## Summary

This PR includes the session's local date and timezone in the
model-visible environment context and persists that data in
`TurnContextItem`.

  ## What changed
- captures the current local date and IANA timezone when building a turn
context, with a UTC fallback if the timezone lookup fails
- includes current_date and timezone in the serialized
<environment_context> payload
- stores those fields on TurnContextItem so they survive rollout/history
handling, subagent review threads, and resume flows
- treats date/timezone changes as environment updates, so prompt caching
and context refresh logic do not silently reuse stale time context
- updates tests to validate the new environment fields without depending
on a single hardcoded environment-context string

## test

built a local build and saw it in the rollout file:
```
{"timestamp":"2026-02-26T21:39:50.737Z","type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"<environment_context>\n  <shell>zsh</shell>\n  <current_date>2026-02-26</current_date>\n  <timezone>America/Los_Angeles</timezone>\n</environment_context>"}]}}
```
2026-02-26 23:17:35 +00:00
Celia ChenandGitHub 4f45668106 Revert "Add skill approval event/response (#12633)" (#12811)
This reverts commit https://github.com/openai/codex/pull/12633. We no
longer need this PR, because we favor sending normal exec command
approval server request with `additional_permissions` of skill
permissions instead
2026-02-26 01:02:42 +00:00
Celia ChenandGitHub b6d20748e0 Revert "Ensure shell command skills trigger approval (#12697)" (#12721)
This reverts commit daf0f03ac8.

# External (non-OpenAI) Pull Request Requirements

Before opening this Pull Request, please read the dedicated
"Contributing" markdown file or your PR may be closed:
https://github.com/openai/codex/blob/main/docs/contributing.md

If your PR conforms to our contribution guidelines, replace this text
with a detailed and high quality description of your changes.

Include a link to a bug report or enhancement request.
2026-02-25 22:49:53 +00:00
Celia ChenandGitHub 6a3233da64 Surface skill permission profiles in zsh-fork exec approvals (#12753)
## Summary

- Preserve each skill’s raw permissions block as a permission_profile on
SkillMetadata during skill loading.
- Keep compiling that same metadata into the existing runtime
Permissions object, so current enforcement
    behavior stays intact.
- When zsh-fork intercepts execution of a script that belongs to a
skill, include the skill’s
    permission_profile in the exec approval request.
- This lets approval UIs show the extra filesystem access the skill
declared when prompting for approval.
2026-02-25 01:23:10 -08:00
Celia ChenandGitHub 1151972fb2 feat: add experimental additionalPermissions to v2 command execution approval requests (#12737)
This adds additionalPermissions to the app-server v2
item/commandExecution/requestApproval payload as an experimental field.

The field is now exposed on CommandExecutionRequestApprovalParams and is
populated from the existing core approval event when a command requests
additional sandbox permissions.

This PR also contains changes to make server requests to support
experiment API.

A real app server test client test:

sample payload with experimental flag off:
```
 {
<   "id": 0,
<   "method": "item/commandExecution/requestApproval",
<   "params": {
<     "command": "/bin/zsh -lc 'mkdir -p ~/some/test && touch ~/some/test/file'",
<     "commandActions": [
<       {
<         "command": "mkdir -p '~/some/test'",
<         "type": "unknown"
<       },
<       {
<         "command": "touch '~/some/test/file'",
<         "type": "unknown"
<       }
<     ],
<     "cwd": "/Users/celia/code/codex/codex-rs",
<     "itemId": "call_QLp0LWkQ1XkU6VW9T2vUZFWB",
<     "proposedExecpolicyAmendment": [
<       "mkdir",
<       "-p",
<       "~/some/test"
<     ],
<     "reason": "Do you want to allow creating ~/some/test/file outside the workspace?",
<     "threadId": "019c9309-e209-7d82-a01b-dcf9556a354d",
<     "turnId": "019c9309-e27a-7f33-834f-6011e795c2d6"
<   }
< }
```
with experimental flag on: 
```
< {
<   "id": 0,
<   "method": "item/commandExecution/requestApproval",
<   "params": {
<     "additionalPermissions": {
<       "fileSystem": null,
<       "macos": null,
<       "network": true
<     },
<     "command": "/bin/zsh -lc 'install -D /dev/null ~/some/test/file'",
<     "commandActions": [
<       {
<         "command": "install -D /dev/null '~/some/test/file'",
<         "type": "unknown"
<       }
<     ],
<     "cwd": "/Users/celia/code/codex/codex-rs",
<     "itemId": "call_K3U4b3dRbj3eMCqslmncbGsq",
<     "proposedExecpolicyAmendment": [
<       "install",
<       "-D"
<     ],
<     "reason": "Do you want to allow creating the file at ~/some/test/file outside the workspace sandbox?",
<     "threadId": "019c9303-3a8e-76e1-81bf-d67ac446d892",
<     "turnId": "019c9303-3af1-7143-88a1-73132f771234"
<   }
< }
```
2026-02-25 05:16:35 +00:00
Celia ChenandGitHub 16ca527c80 chore: migrate additional permissions to PermissionProfile (#12731)
This PR replaces the old `additional_permissions.fs_read/fs_write` shape
with a shared `PermissionProfile`
model and wires it through the command approval, sandboxing, protocol,
and TUI layers. The schema is adopted from the
`SkillManifestPermissions`, which is also refactored to use this unified
struct. This helps us easily expose permission profiles in app
server/core as a follow-up.
2026-02-25 03:35:28 +00:00
Celia ChenandGitHub 5b6911cb1b feat(skills): add permission profiles from openai.yaml metadata (#11658)
## Summary

This PR adds support for skill-level permissions in .codex/openai.yaml
and wires that through the skill loading pipeline.

  ## What’s included

1. Added a new permissions section for skills (network, filesystem, and
macOS-related access).
2. Implemented permission parsing/normalization and translation into
runtime permission profiles.
3. Threaded the new permission profile through SkillMetadata and loader
flow.

  ## Follow-up

A follow-up PR will connect these permission profiles to actual sandbox
enforcement and add user approval prompts for executing binaries/scripts
from skill directories.


 ## Example 
`openai.yaml` snippet:
```
  permissions:
    network: true
    fs_read:
      - "./data"
      - "./data"
    fs_write:
      - "./output"
    macos_preferences: "readwrite"
    macos_automation:
      - "com.apple.Notes"
    macos_accessibility: true
    macos_calendar: true
```

compiled skill permission profile metadata (macOS): 
```
SkillPermissionProfile {
      sandbox_policy: SandboxPolicy::WorkspaceWrite {
          writable_roots: vec![
              AbsolutePathBuf::try_from("/ABS/PATH/TO/SKILL/output").unwrap(),
          ],
          read_only_access: ReadOnlyAccess::Restricted {
              include_platform_defaults: true,
              readable_roots: vec![
                  AbsolutePathBuf::try_from("/ABS/PATH/TO/SKILL/data").unwrap(),
              ],
          },
          network_access: true,
          exclude_tmpdir_env_var: false,
          exclude_slash_tmp: false,
      },
      // Truncated for readability; actual generated profile is longer.
      macos_seatbelt_permission_file: r#"
  (allow user-preference-write)
  (allow appleevent-send
      (appleevent-destination "com.apple.Notes"))
  (allow mach-lookup (global-name "com.apple.axserver"))
  (allow mach-lookup (global-name "com.apple.CalendarAgent"))
  ...
  "#.to_string(),
```
2026-02-14 01:43:44 +00:00
Celia ChenandGitHub dfd1e199a0 [feat] add seatbelt permission files (#11639)
Add seatbelt permission extension abstraction as permission files for
seatbelt profiles. This should complement our current sandbox policy
2026-02-12 23:30:22 +00:00
Celia ChenandGitHub 641d5268fa chore: persist turn_id in rollout session and make turn_id uuid based (#11246)
Problem:
1. turn id is constructed in-memory;
2. on resuming threads, turn_id might not be unique;
3. client cannot no the boundary of a turn from rollout files easily.

This PR does three things:
1. persist `task_started` and `task_complete` events;
1. persist `turn_id` in rollout turn events;
5. generate turn_id as unique uuids instead of incrementing it in
memory.

This helps us resolve the issue of clients wanting to have unique turn
ids for resuming a thread, and knowing the boundry of each turn in
rollout files.

example debug logs
```
2026-02-11T00:32:10.746876Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=8 turn=Turn { id: "019c4a07-d809-74c3-bc4b-fd9618487b4b", items: [UserMessage { id: "item-24", content: [Text { text: "hi", text_elements: [] }] }, AgentMessage { id: "item-25", text: "Hi. I’m in the workspace with your current changes loaded and ready. Send the next task and I’ll execute it end-to-end." }], status: Completed, error: None }
2026-02-11T00:32:10.746888Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=9 turn=Turn { id: "019c4a18-1004-76c0-a0fb-a77610f6a9b8", items: [UserMessage { id: "item-26", content: [Text { text: "hello", text_elements: [] }] }, AgentMessage { id: "item-27", text: "Hello. Ready for the next change in `codex-rs`; I can continue from the current in-progress diff or start a new task." }], status: Completed, error: None }
2026-02-11T00:32:10.746899Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=10 turn=Turn { id: "019c4a19-41f0-7db0-ad78-74f1503baeb8", items: [UserMessage { id: "item-28", content: [Text { text: "hello", text_elements: [] }] }, AgentMessage { id: "item-29", text: "Hello. Send the specific change you want in `codex-rs`, and I’ll implement it and run the required checks." }], status: Completed, error: None }
```

backward compatibility:
if you try to resume an old session without task_started and
task_complete event populated, the following happens:
- If you resume and do nothing: those reconstructed historical IDs can
differ next time you resume.
- If you resume and send a new turn: the new turn gets a fresh UUID from
live submission flow and is persisted, so that new turn’s ID is stable
on later resumes.
I think this behavior is fine, because we only care about deterministic
turn id once a turn is triggered.
2026-02-11 03:56:01 +00:00
Celia ChenandGitHub 16647b188b chore: add codex debug app-server tooling (#10367)
codex debug app-server <user message> forwards the message through
codex-app-server-test-client’s send_message_v2 library entry point,
using std::env::current_exe() to resolve the codex binary.

for how it looks like, see:

```
celia@com-92114 codex-rs % cargo build -p codex-cli && target/debug/codex debug app-server --help                       
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.34s
Tooling: helps debug the app server

Usage: codex debug app-server [OPTIONS] <COMMAND>

Commands:
  send-message-v2  
  help             Print this message or the help of the given subcommand(s)
````
and
```
celia@com-92114 codex-rs % cargo build -p codex-cli && target/debug/codex debug app-server send-message-v2 "hello world"
   Compiling codex-cli v0.0.0 (/Users/celia/code/codex/codex-rs/cli)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.38s
> {
>   "method": "initialize",
>   "id": "f8ba9f60-3a49-4ea9-81d6-4ab6853e3954",
>   "params": {
>     "clientInfo": {
>       "name": "codex-toy-app-server",
>       "title": "Codex Toy App Server",
>       "version": "0.0.0"
>     },
>     "capabilities": {
>       "experimentalApi": true
>     }
>   }
> }
< {
<   "id": "f8ba9f60-3a49-4ea9-81d6-4ab6853e3954",
<   "result": {
<     "userAgent": "codex-toy-app-server/0.0.0 (Mac OS 26.2.0; arm64) vscode/2.4.27 (codex-toy-app-server; 0.0.0)"
<   }
< }
< initialize response: InitializeResponse { user_agent: "codex-toy-app-server/0.0.0 (Mac OS 26.2.0; arm64) vscode/2.4.27 (codex-toy-app-server; 0.0.0)" }
> {
>   "method": "thread/start",
>   "id": "203f1630-beee-4e60-b17b-9eff16b1638b",
>   "params": {
>     "model": null,
>     "modelProvider": null,
>     "cwd": null,
>     "approvalPolicy": null,
>     "sandbox": null,
>     "config": null,
>     "baseInstructions": null,
>     "developerInstructions": null,
>     "personality": null,
>     "ephemeral": null,
>     "dynamicTools": null,
>     "mockExperimentalField": null,
>     "experimentalRawEvents": false
>   }
> }
...
```
2026-02-03 23:17:34 +00:00
Celia ChenandGitHub fb2df99cf1 [feat] persist thread_dynamic_tools in db (#10252)
Persist thread_dynamic_tools in sqlite and read first from it. Fall back
to rollout files if it's not found. Persist dynamic tools to both sqlite
and rollout files.

Saw that new sessions get populated to db correctly & old sessions get
backfilled correctly at startup:
```
celia@com-92114 codex-rs % sqlite3 ~/.codex/state.sqlite \      "select thread_id, position,name,description,input_schema from thread_dynamic_tools;"
019c0cad-ec0d-74b2-a787-e8b33a349117|0|geo_lookup|lookup a city|{"properties":{"city":{"type":"string"}},"required":["city"],"type":"object"}
....
019c10ca-aa4b-7620-ae40-c0919fbd7ea7|0|geo_lookup|lookup a city|{"properties":{"city":{"type":"string"}},"required":["city"],"type":"object"}
```
2026-02-03 00:06:44 +00:00
Celia ChenandGitHub 7151387474 [feat] persist dynamic tools in session rollout file (#10130)
Add dynamic tools to rollout file for persistence & read from rollout on
resume. Ran a real example and spotted the following in the rollout
file:
```
{"timestamp":"2026-01-29T01:27:57.468Z","type":"session_meta","payload":{"id":"019c075d-3f0b-77e3-894e-c1c159b04b1e","timestamp":"2026-01-29T01:27:57.451Z","...."dynamic_tools":[{"name":"demo_tool","description":"Demo dynamic tool","inputSchema":{"additionalProperties":false,"properties":{"city":{"type":"string"}},"required":["city"],"type":"object"}}],"git":{"commit_hash":"ebc573f15c01b8af158e060cfedd401f043e9dfa","branch":"dev/cc/dynamic-tools","repository_url":"https://github.com/openai/codex.git"}}}
```
2026-01-30 01:10:00 +00:00
Celia ChenandGitHub 02f67bace8 fix: Emit response.completed immediately for Responses SSE (#9170)
we see windows test failures like this:
https://github.com/openai/codex/actions/runs/20930055601/job/60138344260.

The issue is that SSE connections sometimes remain open after the
completion event esp. for windows. We should emit the completion event
and return immediately. this is consistent with the protocol:

> The Model streams responses back in an SSE, which are collected until
"completed" message and the SSE terminates

from
https://github.com/openai/codex/blob/dev/cc/fix-windows-test/codex-rs/docs/protocol_v1.md#L37.

this helps us achieve parity with responses websocket logic here:
https://github.com/openai/codex/blob/dev/cc/fix-windows-test/codex-rs/codex-api/src/endpoint/responses_websocket.rs#L220-L227.
2026-01-14 10:05:00 -08:00
Celia ChenandGitHub be4364bb80 [chore] move app server tests from chat completion to responses (#8939)
We are deprecating chat completions. Move all app server tests from chat
completion to responses.
2026-01-08 22:27:55 +00:00
Celia ChenandGitHub 051bf81df9 [fix] app server flaky send_messages test (#8874)
Fix flakiness of CI test:
https://github.com/openai/codex/actions/runs/20350530276/job/58473691434?pr=8282

This PR does two things:
1. move the flakiness test to use responses API instead of chat
completion API
2. make mcp_process agnostic to the order of
responses/notifications/requests that come in, by buffering messages not
read
2026-01-08 20:41:21 +00:00
Celia ChenandGitHub c4af304c77 [fix] app server flaky thread/resume tests (#8870)
Fix flakiness of CI tests:
https://github.com/openai/codex/actions/runs/20350530276/job/58473691443?pr=8282

This PR does two things:
1. test with responses API instead of chat completions API in
thread_resume tests;
2. have a new responses API fixture that mocks out arbitrary numbers of
responses API calls (including no calls) and have the same repeated
response.

Tested by CI
2026-01-08 10:17:05 -08:00
Celia ChenandGitHub e8421c761c [chore] update app server doc with skills (#8853) 2026-01-07 20:07:01 +00:00
11d4f3f45e [app-server] fix config loading for conversations (#8765)
Currently we don't load config properly for app server conversations.
see:
https://linear.app/openai/issue/CODEX-3956/config-flags-not-respected-in-codex-app-server.
This PR fixes that by respecting the config passed in.

Tested by running `cargo build -p codex-cli &&
RUST_LOG=codex_app_server=debug CODEX_BIN=target/debug/codex cargo run
-p codex-app-server-test-client -- \
--config
model_providers.mock_provider.base_url=\"http://localhost:4010/v2\" \
    --config model_provider=\"mock_provider\" \
    --config model_providers.mock_provider.name="hello" \
    send-message-v2 "hello"`
and verified that the mock_provider is called instead of default
provider.

#closes
https://linear.app/openai/issue/CODEX-3956/config-flags-not-respected-in-codex-app-server

---------

Co-authored-by: Michael Bolin <mbolin@openai.com>
2026-01-06 22:02:17 +00:00
Celia ChenandGitHub 2e5d52cb14 [release] Add a dmg target for MacOS (#8207)
Add a dmg target that bundles the codex and codex responses api proxy
binaries for MacOS. this target is signed and notarized.

Verified by triggering a build here:
https://github.com/openai/codex/actions/runs/20318136302/job/58367155205.
Downloaded the artifact and verified that the dmg is signed and
notarized, and the codex binary contained works as expected.
2025-12-18 11:19:10 -08:00
Celia ChenandGitHub 70913effc3 [app-server] add new RawResponseItem v2 event (#8152)
``codex/event/raw_response_item` (v1) -> `rawResponseItem/completed`
(v1).

test client log:
````
< {
<   "method": "codex/event/raw_response_item",
<   "params": {
<     "conversationId": "019b29f7-b089-7140-a535-3fe681562c15",
<     "id": "0",
<     "msg": {
<       "item": {
<         "arguments": "{\"command\":\"sed -n '1,160p' Cargo.toml\",\"workdir\":\"/Users/celia/code/codex/codex-rs\"}",
<         "call_id": "call_DrqbdB2jPxezPWc19YVEEt3h",
<         "name": "shell_command",
<         "type": "function_call"
<       },
<       "type": "raw_response_item"
<     }
<   }
< }
< {
<   "method": "rawResponseItem/completed",
<   "params": {
<     "item": {
<       "arguments": "{\"command\":\"sed -n '1,160p' Cargo.toml\",\"workdir\":\"/Users/celia/code/codex/codex-rs\"}",
<       "call_id": "call_DrqbdB2jPxezPWc19YVEEt3h",
<       "name": "shell_command",
<       "type": "function_call"
<     },
<     "threadId": "019b29f7-b089-7140-a535-3fe681562c15",
<     "turnId": "0"
<   }
< }
```
2025-12-17 02:19:30 +00:00
Celia ChenandGitHub ce19dbbb22 [app-server] Update readme to include mcp endpoints (#7850)
n/a
2025-12-11 01:08:31 +00:00
Celia ChenandGitHub 7cabe54fc7 [app-server] make app server not throw error when login id is not found (#7831)
Our previous design of cancellation endpoint is not idempotent, which
caused a bunch of flaky tests. Make app server just returned a not_found
status instead of throwing an error if the login id is not found. Keep
V1 endpoint behavior the same.
2025-12-10 16:19:40 -08:00
Celia ChenandGitHub bfb4d5710b [app-server-protocol] Add types for config (#7658)
Currently the config returned by `config/read` in untyped. Add types so
it's easier for client to parse the config. Since currently configs are
all defined in snake case we'll keep that instead of using camel case
like the rest of V2.

Sample output by testing using the app server test client:
```
{
<   "id": "f28449f4-b015-459b-b07b-eef06980165d",
<   "result": {
<     "config": {
<       "approvalPolicy": null,
<       "compactPrompt": null,
<       "developerInstructions": null,
<       "features": {
<         "experimental_use_rmcp_client": true
<       },
<       "forcedChatgptWorkspaceId": null,
<       "forcedLoginMethod": null,
<       "instructions": null,
<       "model": "gpt-5.1-codex-max",
<       "modelAutoCompactTokenLimit": null,
<       "modelContextWindow": null,
<       "modelProvider": null,
<       "modelReasoningEffort": null,
<       "modelReasoningSummary": null,
<       "modelVerbosity": null,
<       "model_providers": {
<         "local": {
<           "base_url": "http://localhost:8061/api/codex",
<           "env_http_headers": {
<             "ChatGPT-Account-ID": "OPENAI_ACCOUNT_ID"
<           },
<           "env_key": "CHATGPT_TOKEN_STAGING",
<           "name": "local",
<           "wire_api": "responses"
<         }
<       },
<       "model_reasoning_effort": "medium",
<       "notice": {
<         "hide_gpt-5.1-codex-max_migration_prompt": true,
<         "hide_gpt5_1_migration_prompt": true
<       },
<       "profile": null,
<       "profiles": {},
<       "projects": {
<         "/Users/celia/code": {
<           "trust_level": "trusted"
<         },
<         "/Users/celia/code/codex": {
<           "trust_level": "trusted"
<         },
<         "/Users/celia/code/openai": {
<           "trust_level": "trusted"
<         }
<       },
<       "reviewModel": null,
<       "sandboxMode": null,
<       "sandboxWorkspaceWrite": null,
<       "tools": {
<         "viewImage": null,
<         "webSearch": null
<       }
<     },
<     "origins": {
<       "features.experimental_use_rmcp_client": {
<         "name": "user",
<         "source": "/Users/celia/.codex/config.toml",
<         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
<       },
<       "model": {
<         "name": "user",
<         "source": "/Users/celia/.codex/config.toml",
<         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
<       },
<       "model_providers.local.base_url": {
<         "name": "user",
<         "source": "/Users/celia/.codex/config.toml",
<         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
<       },
<       "model_providers.local.env_http_headers.ChatGPT-Account-ID": {
<         "name": "user",
<         "source": "/Users/celia/.codex/config.toml",
<         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
<       },
<       "model_providers.local.env_key": {
<         "name": "user",
<         "source": "/Users/celia/.codex/config.toml",
<         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
<       },
<       "model_providers.local.name": {
<         "name": "user",
<         "source": "/Users/celia/.codex/config.toml",
<         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
<       },
<       "model_providers.local.wire_api": {
<         "name": "user",
<         "source": "/Users/celia/.codex/config.toml",
<         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
<       },
<       "model_reasoning_effort": {
<         "name": "user",
<         "source": "/Users/celia/.codex/config.toml",
<         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
<       },
<       "notice.hide_gpt-5.1-codex-max_migration_prompt": {
<         "name": "user",
<         "source": "/Users/celia/.codex/config.toml",
<         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
<       },
<       "notice.hide_gpt5_1_migration_prompt": {
<         "name": "user",
<         "source": "/Users/celia/.codex/config.toml",
<         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
<       },
<       "projects./Users/celia/code.trust_level": {
<         "name": "user",
<         "source": "/Users/celia/.codex/config.toml",
<         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
<       },
<       "projects./Users/celia/code/codex.trust_level": {
<         "name": "user",
<         "source": "/Users/celia/.codex/config.toml",
<         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
<       },
<       "projects./Users/celia/code/openai.trust_level": {
<         "name": "user",
<         "source": "/Users/celia/.codex/config.toml",
<         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
<       },
<       "tools.web_search": {
<         "name": "user",
<         "source": "/Users/celia/.codex/config.toml",
<         "version": "sha256:a1d8eaedb5d9db5dfdfa69f30fa9df2efec66bb4dd46aa67f149fcc67cd0711c"
<       }
<     }
<   }
< }
```
2025-12-10 21:35:31 +00:00
Celia ChenandGitHub 8a71f8b634 [app-server] Make sure that config writes preserve comments & order or configs (#7789)
Make sure that config writes preserve comments and order of configs by
utilizing the ConfigEditsBuilder in core.

Tested by running a real example and made sure that nothing in the
config file changes other than the configs to edit.
2025-12-10 19:14:27 +00:00
Celia ChenandGitHub 3e6cd5660c [app-server] make file_path for config optional (#7560)
When we are writing to config using `config/value/write` or
`config/batchWrite`, it always require a `config/read` before it right
now in order to get the correct file path to write to. make this
optional so we read from the default user config file if this is not
passed in.
2025-12-04 03:08:18 +00:00
Celia ChenandGitHub ff4ca9959c [app-server] Add ImageView item (#7468)
Add view_image tool call as image_view item.

Before:
```
< {
<   "method": "codex/event/view_image_tool_call",
<   "params": {
<     "conversationId": "019adc2f-2922-7e43-ace9-64f394019616",
<     "id": "0",
<     "msg": {
<       "call_id": "call_nBQDxnTfZQtgjGpVoGuDnRjz",
<       "path": "/Users/celia/code/codex/codex-rs/app-server-protocol/codex-cli-login.png",
<       "type": "view_image_tool_call"
<     }
<   }
< }
```

After:
```
< {
<   "method": "item/started",
<   "params": {
<     "item": {
<       "id": "call_nBQDxnTfZQtgjGpVoGuDnRjz",
<       "path": "/Users/celia/code/codex/codex-rs/app-server-protocol/codex-cli-login.png",
<       "type": "imageView"
<     },
<     "threadId": "019adc2f-2922-7e43-ace9-64f394019616",
<     "turnId": "0"
<   }
< }

< {
<   "method": "item/completed",
<   "params": {
<     "item": {
<       "id": "call_nBQDxnTfZQtgjGpVoGuDnRjz",
<       "path": "/Users/celia/code/codex/codex-rs/app-server-protocol/codex-cli-login.png",
<       "type": "imageView"
<     },
<     "threadId": "019adc2f-2922-7e43-ace9-64f394019616",
<     "turnId": "0"
<   }
< }
```
2025-12-01 23:56:05 +00:00
Celia ChenandGitHub 40006808a3 [app-server] add turn/plan/updated event (#7329)
transform `EventMsg::PlanDate` to v2 `turn/plan/updated` event. similar
to `turn/diff/updated`.
2025-11-30 21:09:59 -08:00
Celia ChenandGitHub 401f94ca31 [app-server] add thread/tokenUsage/updated v2 event (#7268)
the TokenEvent event message becomes `thread/tokenUsage/updated` in v2.
before & after:
```
< {
<   "method": "codex/event/token_count",
<   "params": {
<     "conversationId": "019ab891-4c55-7790-9670-6c3b48c33281",
<     "id": "1",
<     "msg": {
<       "info": {
<         "last_token_usage": {
<           "cached_input_tokens": 3072,
<           "input_tokens": 5152,
<           "output_tokens": 16,
<           "reasoning_output_tokens": 0,
<           "total_tokens": 5168
<         },
<         "model_context_window": 258400,
<         "total_token_usage": {
<           "cached_input_tokens": 3072,
<           "input_tokens": 5152,
<           "output_tokens": 16,
<           "reasoning_output_tokens": 0,
<           "total_tokens": 5168
<         }
<       },
<       "rate_limits": {
<         "credits": null,
<         "primary": null,
<         "secondary": null
<       },
<       "type": "token_count"
<     }
<   }
< }
< {
<   "method": "thread/tokenUsage/updated",
<   "params": {
<     "threadId": "019ab891-4c55-7790-9670-6c3b48c33281",
<     "tokenUsage": {
<       "last": {
<         "cachedInputTokens": 3072,
<         "inputTokens": 5152,
<         "outputTokens": 16,
<         "reasoningOutputTokens": 0,
<         "totalTokens": 5168
<       },
<       "modelContextWindow": 258400,
<       "total": {
<         "cachedInputTokens": 3072,
<         "inputTokens": 5152,
<         "outputTokens": 16,
<         "reasoningOutputTokens": 0,
<         "totalTokens": 5168
<       }
<     },
<     "turnId": "1"
<   }
< }
```
2025-11-25 19:56:04 +00:00
Celia ChenandGitHub 0dd822264a [app-server-test-client] add send-followup-v2 (#7271)
Add a new endpoint that allows us to test multi-turn behavior.

Tested with running:
```
RUST_LOG=codex_app_server=debug CODEX_BIN=target/debug/codex \
      cargo run -p codex-app-server-test-client -- \
      send-follow-up-v2 "hello" "and now a follow-up question"
```
2025-11-25 08:04:27 +00:00
Celia ChenandGitHub 7e2165f394 [app-server] update doc with codex error info (#6941)
Document new codex error info. Also fixed the name from
`codex_error_code` to `codex_error_info`.
2025-11-21 01:02:37 +00:00
Celia ChenandGitHub 9bce050385 [app-server & core] introduce new codex error code and v2 app-server error events (#6938)
This PR does two things:
1. populate a new `codex_error_code` protocol in error events sent from
core to client;
2. old v1 core events `codex/event/stream_error` and `codex/event/error`
will now both become `error`. We also show codex error code for
turncompleted -> error status.

new events in app server test:
```
< {
<   "method": "codex/event/stream_error",
<   "params": {
<     "conversationId": "019aa34c-0c14-70e0-9706-98520a760d67",
<     "id": "0",
<     "msg": {
<       "codex_error_code": {
<         "response_stream_disconnected": {
<           "http_status_code": 401
<         }
<       },
<       "message": "Reconnecting... 2/5",
<       "type": "stream_error"
<     }
<   }
< }

 {
<   "method": "error",
<   "params": {
<     "error": {
<       "codexErrorCode": {
<         "responseStreamDisconnected": {
<           "httpStatusCode": 401
<         }
<       },
<       "message": "Reconnecting... 2/5"
<     }
<   }
< }

< {
<   "method": "turn/completed",
<   "params": {
<     "turn": {
<       "error": {
<         "codexErrorCode": {
<           "responseTooManyFailedAttempts": {
<             "httpStatusCode": 401
<           }
<         },
<         "message": "exceeded retry limit, last status: 401 Unauthorized, request id: 9a1b495a1a97ed3e-SJC"
<       },
<       "id": "0",
<       "items": [],
<       "status": "failed"
<     }
<   }
< }
```
2025-11-20 23:06:55 +00:00
Celia ChenandGitHub 72a1453ac5 Revert "[core] add optional status_code to error events (#6865)" (#6955)
This reverts commit c2ec477d93.

# External (non-OpenAI) Pull Request Requirements

Before opening this Pull Request, please read the dedicated
"Contributing" markdown file or your PR may be closed:
https://github.com/openai/codex/blob/main/docs/contributing.md

If your PR conforms to our contribution guidelines, replace this text
with a detailed and high quality description of your changes.

Include a link to a bug report or enhancement request.
2025-11-20 01:26:14 +00:00
Celia ChenandGitHub c2ec477d93 [core] add optional status_code to error events (#6865)
We want to better uncover error status code for clients. Add an optional
status_code to error events (thread error, error, stream error) so app
server could uncover the status code from the client side later.

in event log:
```
< {
<   "method": "codex/event/stream_error",
<   "params": {
<     "conversationId": "019a9a32-f576-7292-9711-8e57e8063536",
<     "id": "0",
<     "msg": {
<       "message": "Reconnecting... 5/5",
<       "status_code": 401,
<       "type": "stream_error"
<     }
<   }
< }
< {
<   "method": "codex/event/error",
<   "params": {
<     "conversationId": "019a9a32-f576-7292-9711-8e57e8063536",
<     "id": "0",
<     "msg": {
<       "message": "exceeded retry limit, last status: 401 Unauthorized, request id: 9a0cb03a485067f7-SJC",
<       "status_code": 401,
<       "type": "error"
<     }
<   }
< }
```
2025-11-19 19:51:21 +00:00
Celia ChenandGitHub b395dc1be6 [app-server] introduce turn/completed v2 event (#6800)
similar to logic in
`codex/codex-rs/exec/src/event_processor_with_jsonl_output.rs`.
translation of v1 -> v2 events:
`codex/event/task_complete` -> `turn/completed`
`codex/event/turn_aborted` -> `turn/completed` with `interrupted` status
`codex/event/error` -> `turn/completed` with `error` status

this PR also makes `items` field in `Turn` optional. For now, we only
populate it when we resume a thread, and leave it as None for all other
places until we properly rewrite core to keep track of items.

tested using the codex app server client. example new event:
```
< {
<   "method": "turn/completed",
<   "params": {
<     "turn": {
<       "id": "0",
<       "items": [],
<       "status": "interrupted"
<     }
<   }
< }
```
2025-11-19 01:55:24 +00:00
Celia ChenandGitHub 842a1b7fe7 [app-server] add events to readme (#6690)
add table of contents, lifecycle and events to readme.
2025-11-17 19:28:05 +00:00
Celia ChenandGitHub 526777c9b4 [App server] add mcp tool call item started/completed events (#6642)
this PR does two things:
1. refactor `apply_bespoke_event_handling` into a separate file as it's
getting kind of long;
2. add mcp tool call `item/started` and `item/completed` events. To roll
out app server events asap we didn't properly migrate mcp core events to
use TurnItem for mcp tool calls - this will be a follow-up PR.

real events generated in log:
```
{
  "method": "codex/event/mcp_tool_call_end",
  "params": {
    "conversationId": "019a8021-26af-7c20-83db-21ca81e44d68",
    "id": "0",
    "msg": {
      "call_id": "call_7EjRQkD9HnfyMWf7tGrT9FKA",
      "duration": {
        "nanos": 92708,
        "secs": 0
      },
      "invocation": {
        "arguments": {
          "server": ""
        },
        "server": "codex",
        "tool": "list_mcp_resources"
      },
      "result": {
        "Ok": {
          "content": [
            {
              "text": "{\"resources\":[]}",
              "type": "text"
            }
          ],
          "isError": false
        }
      },
      "type": "mcp_tool_call_end"
    }
  }
}

{
  "method": "item/completed",
  "params": {
    "item": {
      "arguments": {
        "server": ""
      },
      "error": null,
      "id": "call_7EjRQkD9HnfyMWf7tGrT9FKA",
      "result": {
        "content": [
          {
            "text": "{\"resources\":[]}",
            "type": "text"
          }
        ],
        "structuredContent": null
      },
      "server": "codex",
      "status": "completed",
      "tool": "list_mcp_resources",
      "type": "mcpToolCall"
    }
  }
}
```
2025-11-14 08:08:43 -08:00
Celia ChenandGitHub b8ec97c0ef [App-server] add new v2 events:item/reasoning/delta, item/agentMessage/delta & item/reasoning/summaryPartAdded (#6559)
core event to app server event mapping:
1. `codex/event/reasoning_content_delta` ->
`item/reasoning/summaryTextDelta`.
2. `codex/event/reasoning_raw_content_delta` ->
`item/reasoning/textDelta`
3. `codex/event/agent_message_content_delta` →
`item/agentMessage/delta`.
4. `codex/event/agent_reasoning_section_break` ->
`item/reasoning/summaryPartAdded`.

Also added a change in core to pass down content index, summary index
and item id from events.

Tested with the `git checkout owen/app_server_test_client && cargo run
-p codex-app-server-test-client -- send-message-v2 "hello"` and verified
that new events are emitted correctly.
2025-11-14 00:25:01 +00:00
Celia ChenandGitHub e357fc723d [app-server] add item started/completed events for turn items (#6517)
This one should be quite straightforward, as it's just a translation of
TurnItem events we already emit to ThreadItem that app-server exposes to
customers.

To test, cp my change to owen/app_server_test_client and do the
following:
```
cargo build -p codex-cli
RUST_LOG=codex_app_server=info CODEX_BIN=target/debug/codex cargo run -p codex-app-server-test-client -- send-message-v2 "hello"
```

example event before (still kept there for backward compatibility):
```
{
<   "method": "codex/event/item_completed",
<   "params": {
<     "conversationId": "019a74cc-fad9-7ab3-83a3-f42827b7b074",
<     "id": "0",
<     "msg": {
<       "item": {
<         "Reasoning": {
<           "id": "rs_03d183492e07e20a016913a936eb8c81a1a7671a103fee8afc",
<           "raw_content": [],
<           "summary_text": [
<             "Hey! What would you like to work on? I can explore the repo, run specific tests, or implement a change. Let's keep it short and straightforward. There's no need for a lengthy introduction or elaborate planning, just a friendly greeting and an open offer to help. I want to make sure the user feels welcomed and understood right from the start. It's all about keeping the tone friendly and concise!"
<           ]
<         }
<       },
<       "thread_id": "019a74cc-fad9-7ab3-83a3-f42827b7b074",
<       "turn_id": "0",
<       "type": "item_completed"
<     }
<   }
< }
```

after (v2):
```
< {
<   "method": "item/completed",
<   "params": {
<     "item": {
<       "id": "rs_03d183492e07e20a016913a936eb8c81a1a7671a103fee8afc",
<       "text": "Hey! What would you like to work on? I can explore the repo, run specific tests, or implement a change. Let's keep it short and straightforward. There's no need for a lengthy introduction or elaborate planning, just a friendly greeting and an open offer to help. I want to make sure the user feels welcomed and understood right from the start. It's all about keeping the tone friendly and concise!",
<       "type": "reasoning"
<     }
<   }
< }
```
2025-11-11 22:43:24 +00:00
Celia ChenandGitHub 6951872776 [hygiene][app-server] have a helper function for duplicate code in turn APIs (#6488)
turn_start and turn_interrupt have some logic that can be shared. have a
helper function for it.
2025-11-11 02:44:47 +00:00
Celia ChenandGitHub db408b9e62 [App-server] add initialization to doc (#6377)
Address comments in #6353.
2025-11-07 23:52:20 +00:00
Celia ChenandGitHub 2e81f1900d [App-server] Add auth v2 doc & update codex mcp interface auth section (#6353)
Added doc for auth v2 endpoints. Updated the auth section in Codex MCP
interface doc too.
2025-11-07 08:17:19 -08:00
Celia ChenandGitHub e84e39940b [App-server] Implement account/read endpoint (#6336)
This PR does two things:
1. add a new function in core that maps the core-internal plan type to
the external plan type;
2. implement account/read that get account status (v2 of
`getAuthStatus`).
2025-11-06 19:43:13 -08:00
Celia ChenandGitHub 229d18f4d2 [App-server] Add account/login/cancel v2 endpoint (#6288)
Add `account/login/cancel` v2 endpoint for auth. this is similar
implementation to `cancelLoginChatgpt` v1 endpoint.
2025-11-06 01:13:55 +00:00
Celia ChenandGitHub 05f0b4f590 [App-server] Implement v2 for account/login/start and account/login/completed (#6183)
This PR implements `account/login/start` and `account/login/completed`.
Instead of having separate endpoints for login with chatgpt and api, we
have a single enum handling different login methods. For sync auth
methods like sign in with api key, we still send a `completed`
notification back to be compatible with the async login flow.
2025-11-05 13:52:50 -08:00
Celia ChenandGitHub d3187dbc17 [App-server] v2 for account/updated and account/logout (#6175)
V2 for `account/updated` and `account/logout` for app server. correspond
to old `authStatusChange` and `LogoutChatGpt` respectively. Followup PRs
will make other v2 endpoints call `account/updated` instead of
`authStatusChange` too.
2025-11-03 22:01:33 -08:00
Celia ChenandGitHub 6ef658a9f9 [Hygiene] Remove include_view_image_tool config (#5976)
There's still some debate about whether we want to expose
`tools.view_image` or `feature.view_image` so those are left unchanged
for now, but this old `include_view_image_tool` config is good-to-go.
Also updated the doc to reflect that `view_image` tool is now by default
true.
2025-10-30 13:23:24 -07:00
Celia ChenandGitHub 4a42c4e142 [Auth] Choose which auth storage to use based on config (#5792)
This PR is a follow-up to #5591. It allows users to choose which auth
storage mode they want by using the new
`cli_auth_credentials_store_mode` config.
2025-10-27 19:41:49 -07:00
Celia ChenandGitHub 0fc295d958 [Auth] Add keyring support for Codex CLI (#5591)
Follow-up PR to #5569. Add Keyring Support for Auth Storage in Codex CLI
as well as a hybrid mode (default to persisting in keychain but fall
back to file when unavailable.)

It also refactors out the keyringstore implementation from rmcp-client
[here](https://github.com/openai/codex/blob/main/codex-rs/rmcp-client/src/oauth.rs)
to a new keyring-store crate.

There will be a follow-up that picks the right credential mode depending
on the config, instead of hardcoding `AuthCredentialsStoreMode::File`.
2025-10-27 12:10:11 -07:00
Celia ChenandGitHub eb5b1b627f [Auth] Introduce New Auth Storage Abstraction for Codex CLI (#5569)
This PR introduces a new `Auth Storage` abstraction layer that takes
care of read, write, and load of auth tokens based on the
AuthCredentialsStoreMode. It is similar to how we handle MCP client
oauth
[here](https://github.com/openai/codex/blob/main/codex-rs/rmcp-client/src/oauth.rs).
Instead of reading and writing directly from disk for auth tokens, Codex
CLI workflows now should instead use this auth storage using the public
helper functions.

This PR is just a refactor of the current code so the behavior stays the
same. We will add support for keyring and hybrid mode in follow-up PRs.

I have read the CLA Document and I hereby sign the CLA
2025-10-27 11:01:14 -07:00