Compare commits

...

68 Commits

Author SHA1 Message Date
won
480cfa34da schema.... 2026-03-03 17:33:35 -08:00
won
f0ec57b763 cargo fix 2026-03-03 17:01:09 -08:00
Won Park
539718f721 Merge branch 'main' into image-gen-01 2026-03-03 16:39:17 -08:00
rhan-oai
e951ef4374 [feedback] diagnostics (#13292)
- added header logic to display diagnostics on cli
- added logic for collecting env vars

<img width="606" height="327" alt="Screenshot 2026-03-03 at 3 49 31 PM"
src="https://github.com/user-attachments/assets/05e78c56-8cb3-47fa-abaf-3e57f1fdd8e2"
/>

<img width="690" height="353" alt="Screenshot 2026-03-02 at 6 47 54 PM"
src="https://github.com/user-attachments/assets/e470b559-13f4-44d9-897f-bc398943c6d1"
/>
2026-03-03 16:34:11 -08:00
sayan-oai
082682a628 feat: load plugin apps (#13401)
load plugin-apps from `.app.json`.

make apps runtime-mentionable iff `codex_apps` MCP actually exposes
tools for that `connector_id`.

if the app isn't available, it's filtered out of runtime connector set,
so no tools are added and no app-mentions resolve.

right now we don't have a clean cli-side error for an app not being
installed. can look at this after.

### Tests
Added tests, tested locally that using a plugin that bundles an app
picks up the app.
2026-03-03 16:29:15 -08:00
Curtis 'Fjord' Hawthorne
c4cb594e73 Make js_repl image output controllable (#13331)
## Summary

Instead of always adding inner function call outputs to the model
context, let js code decide which ones to return.

- Stop auto-hoisting nested tool outputs from `codex.tool(...)` into the
outer `js_repl` function output.
- Keep `codex.tool(...)` return values unchanged as structured JS
objects.
- Add `codex.emitImage(...)` as the explicit path for attaching an image
to the outer `js_repl` function output.
- Support emitting from a direct image URL, a single `input_image` item,
an explicit `{ bytes, mimeType }` object, or a raw tool response object
containing exactly one image.
- Preserve existing `view_image` original-resolution behavior when JS
emits the raw `view_image` tool result.
- Suppress the special `ViewImageToolCall` event for `js_repl`-sourced
`view_image` calls so nested inspection stays side-effect free until JS
explicitly emits.
- Update the `js_repl` docs and generated project instructions with both
recommended patterns:
  - `await codex.emitImage(codex.tool("view_image", { path }))`
- `await codex.emitImage({ bytes: await page.screenshot({ type: "jpeg",
quality: 85 }), mimeType: "image/jpeg" })`

#### [git stack](https://github.com/magus/git-stack-cli)
-  `1` https://github.com/openai/codex/pull/13050
- 👉 `2` https://github.com/openai/codex/pull/13331
-  `3` https://github.com/openai/codex/pull/13049
2026-03-03 16:25:59 -08:00
won
9b8cd17a1e cargo fix 2026-03-03 16:18:14 -08:00
alexsong-oai
1afbbc11c3 Ensure the env values of imported shell_environment_policy.set is string (#13402) 2026-03-03 16:12:23 -08:00
Curtis 'Fjord' Hawthorne
b92146d48b Add under-development original-resolution view_image support (#13050)
## Summary

Add original-resolution support for `view_image` behind the
under-development `view_image_original_resolution` feature flag.

When the flag is enabled and the target model is `gpt-5.3-codex` or
newer, `view_image` now preserves original PNG/JPEG/WebP bytes and sends
`detail: "original"` to the Responses API instead of using the legacy
resize/compress path.

## What changed

- Added `view_image_original_resolution` as an under-development feature
flag.
- Added `ImageDetail` to the protocol models and support for serializing
`detail: "original"` on tool-returned images.
- Added `PromptImageMode::Original` to `codex-utils-image`.
  - Preserves original PNG/JPEG/WebP bytes.
  - Keeps legacy behavior for the resize path.
- Updated `view_image` to:
- use the shared `local_image_content_items_with_label_number(...)`
helper in both code paths
  - select original-resolution mode only when:
    - the feature flag is enabled, and
    - the model slug parses as `gpt-5.3-codex` or newer
- Kept local user image attachments on the existing resize path; this
change is specific to `view_image`.
- Updated history/image accounting so only `detail: "original"` images
use the docs-based GPT-5 image cost calculation; legacy images still use
the old fixed estimate.
- Added JS REPL guidance, gated on the same feature flag, to prefer JPEG
at 85% quality unless lossless is required, while still allowing other
formats when explicitly requested.
- Updated tests and helper code that construct
`FunctionCallOutputContentItem::InputImage` to carry the new `detail`
field.

## Behavior

### Feature off
- `view_image` keeps the existing resize/re-encode behavior.
- History estimation keeps the existing fixed-cost heuristic.

### Feature on + `gpt-5.3-codex+`
- `view_image` sends original-resolution images with `detail:
"original"`.
- PNG/JPEG/WebP source bytes are preserved when possible.
- History estimation uses the GPT-5 docs-based image-cost calculation
for those `detail: "original"` images.


#### [git stack](https://github.com/magus/git-stack-cli)
- 👉 `1` https://github.com/openai/codex/pull/13050
-  `2` https://github.com/openai/codex/pull/13331
-  `3` https://github.com/openai/codex/pull/13049
2026-03-03 15:56:54 -08:00
joeytrasatti-openai
935754baa3 Add thread metadata update endpoint to app server (#13280)
## Summary
- add the v2 `thread/metadata/update` API, including
protocol/schema/TypeScript exports and app-server docs
- patch stored thread `gitInfo` in sqlite without resuming the thread,
with validation plus support for explicit `null` clears
- repair missing sqlite thread rows from rollout data before patching,
and make those repairs safe by inserting only when absent and updating
only git columns so newer metadata is not clobbered
- keep sqlite authoritative for mutable thread git metadata by
preserving existing sqlite git fields during reconcile/backfill and only
using rollout `SessionMeta` git fields to fill gaps
- add regression coverage for the endpoint, repair paths, concurrent
sqlite writes, clearing git fields, and rollout/backfill reconciliation
- fix the login server shutdown race so cancelling before the waiter
starts still terminates `block_until_done()` correctly

## Testing
- `cargo test -p codex-state
apply_rollout_items_preserves_existing_git_branch_and_fills_missing_git_fields`
- `cargo test -p codex-state
update_thread_git_info_preserves_newer_non_git_metadata`
- `cargo test -p codex-core
backfill_sessions_preserves_existing_git_branch_and_fills_missing_git_fields`
- `cargo test -p codex-app-server thread_metadata_update`
- `cargo test`
- currently fails in existing `codex-core` grep-files tests with
`unsupported call: grep_files`:
    - `suite::grep_files::grep_files_tool_collects_matches`
    - `suite::grep_files::grep_files_tool_reports_empty_results`
2026-03-03 15:56:11 -08:00
Won Park
b89461693f Merge branch 'main' into image-gen-01 2026-03-03 15:52:08 -08:00
won
15fcd7d0fb comment 2026-03-03 15:33:21 -08:00
won
c5dcb9caa2 added comment 2026-03-03 15:32:01 -08:00
Charley Cunningham
299b8ac445 tui: align pending steers with core acceptance (#12868)
## Summary
- submit `Enter` steers immediately while a turn is already running
instead of routing them through `queued_user_messages`
- keep those submitted steers visible in the footer as `pending_steers`
until core records them as a user message or aborts the turn
- reconcile pending steers on `ItemCompleted(UserMessage)`, not
`RawResponseItem`
- emit user-message item lifecycle for leftover pending input at task
finish, then remove the TUI `TurnComplete` fallback
- keep `queued_user_messages` for actual queued drafts, rendered below
pending steers

## Problem
While the assistant was generating, pressing `Enter` could send the
input into `queued_user_messages`. That queue only drains after the turn
ends, so ordinary steers behaved like queued drafts instead of landing
at the next core sampling boundary.

The first version of this fix also used `RawResponseItem` to decide when
a steer had landed. Review feedback was that this is the wrong
abstraction for client behavior.

There was also a late edge case in core: if pending steer input was
accepted after the final sampling decision but before `TurnComplete`,
core would record that user message into history at task finish without
emitting `ItemStarted(UserMessage)` / `ItemCompleted(UserMessage)`. TUI
had a fallback to paper over that gap locally.

## Approach
- `Enter` during an active turn now submits a normal `Op::UserTurn`
immediately
- TUI keeps a local pending-steer preview instead of rendering that user
message into history immediately
- when core records the steer as `ItemCompleted(UserMessage)`, TUI
matches and removes the corresponding pending preview, then renders the
committed user message
- core now emits the same user-message lifecycle when
`on_task_finished(...)` drains leftover pending user input, before
`TurnComplete`
- with that lifecycle gap closed in core, TUI no longer needs to flush
pending steers into history on `TurnComplete`
- if the turn is interrupted, pending steers and queued drafts are both
restored into the composer, with pending steers first

## Notes
- `Tab` still uses the real queued-message path
- `queued_user_messages` and `pending_steers` are separate state with
separate semantics
- the pending-steer matching key is built directly from `UserInput`
- this removes the new TUI dependency on `RawResponseItem`

## Validation
- `just fmt`
- `cargo test -p codex-core
task_finish_emits_turn_item_lifecycle_for_leftover_pending_user_input --
--nocapture`
- `cargo test -p codex-tui`
2026-03-03 15:31:52 -08:00
won
92ff7ed236 redacted some serdes 2026-03-03 15:28:40 -08:00
won
77c88bbb63 logic changes 2026-03-03 15:12:09 -08:00
viyatb-oai
24a2d0c696 fix(network-proxy): reject mismatched host headers (#13275)
## Summary
- reject plain HTTP absolute-form requests whose Host header does not
match the request target authority
- add host/port-aware Host header validation for non-default ports
- add regression coverage for mismatched Host forwarding and validator
edge cases
2026-03-03 15:12:06 -08:00
xl-openai
9b004e2db1 Refactor plugin config and cache path (#13333)
Update config.toml plugin entries to use
<plugin_name>@<marketplace_name> as the key.
Plugin now stays in
[plugins/cache/marketplace-name/plugin-name/$version/]
Clean up the plugin code structure.
Add plugin install functionality (not used yet).
2026-03-03 15:00:18 -08:00
Ahmed Ibrahim
041c896509 Revert "Revert "realtime prompt changes"" (#13398)
Reverts openai/codex#13385
2026-03-03 14:41:26 -08:00
Eric Traut
bab32afa93 Require deduplicator success before commenting (#13399)
Fixed recent regression in issue dedup action
2026-03-03 15:32:47 -07:00
Ahmed Ibrahim
6bee02a346 Build delegated realtime handoff text from all messages (#13395)
## Summary
- Route delegated realtime handoff turns from all handoff message texts,
preserving order
- Fallback to input_transcript only when no messages are present
- Add regression coverage for multi-message handoff requests
2026-03-03 14:07:51 -08:00
Owen Lin
d7eb195b62 chore(app-server): restore EventMsg TS types (#13397)
Realized EventMsg generated types were unintentionally removed as part
of this PR: https://github.com/openai/codex/pull/13375

Turns out our TypeScript export pipeline relied on transitively reaching
`EventMsg`. We should still export `EventMsg` explicitly since we're
still emitting `codex/event/*` events (for now, but getting dropped soon
as well).
2026-03-03 13:37:40 -08:00
Owen Lin
167158f93c chore(app-server): delete v1 RPC methods and notifications (#13375)
## Summary
This removes the old app-server v1 methods and notifications we no
longer need, while keeping the small set the main codex app client still
depends on for now.

The remaining legacy surface is:
- `initialize`
- `getConversationSummary`
- `getAuthStatus`
- `gitDiffToRemote`
- `fuzzyFileSearch`
- `fuzzyFileSearch/sessionStart`
- `fuzzyFileSearch/sessionUpdate`
- `fuzzyFileSearch/sessionStop`

And the raw `codex/event/*` notifications emitted from core. These
notifications will be removed in a followup PR.

## What changed
- removed deprecated v1 request variants from the protocol and
app-server dispatcher
- removed deprecated typed notifications: `authStatusChange`,
`loginChatGptComplete`, and `sessionConfigured`
- updated the app-server test client to use v2 flows instead of deleted
v1 flows
- deleted legacy-only app-server test suites and added focused coverage
for `getConversationSummary`
- regenerated app-server schema fixtures and updated the MCP interface
docs to match the remaining compatibility surface

## Testing
- `just write-app-server-schema`
- `cargo test -p codex-app-server-protocol`
- `cargo test -p codex-app-server`
2026-03-03 13:18:25 -08:00
Ahmed Ibrahim
72d368e03a fix (#13389)
# 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-03-03 12:48:16 -08:00
Ahmed Ibrahim
8afe2127dc Revert "realtime prompt changes" (#13385)
Reverts openai/codex#13376
2026-03-03 12:30:37 -08:00
Jeremy Rose
c2d008aca5 Collapse parsed command summaries when any stage is unknown (#13043)
## Summary
- collapse parsed command output to a single `Unknown` whenever the
normal parse includes any unknown entry
- preserve the existing parsing flow and existing `cd` handling,
including the current `cd && ...` collapse behavior
- trim redundant tests and add focused coverage for collapse-on-unknown
cases

## Testing
- `cargo test -p codex-shell-command`
2026-03-03 19:45:34 +00:00
sayan-oai
39f00f2a06 chore: rm --all-features flag from rust-analyzer (#13381)
follows up on #12429; rm `--all-features` from flags used with
`rust-analyzer` on save to prevent disk space bloat under `target/`.
2026-03-03 11:44:54 -08:00
Charley Cunningham
c4bd0aa3b9 app-server: source /feedback logs from sqlite at trace level (#12969)
## Summary
- write app-server SQLite logs at TRACE level when SQLite is enabled
- source app-server `/feedback` log attachments from SQLite for the
requested thread when available
- flush buffered SQLite log writes before `/feedback` queries them so
newly emitted events are not lost behind the async inserter
- include same-process threadless SQLite rows in those `/feedback` logs
so the attachment matches the process-wide feedback buffer more closely
- keep the existing in-memory ring buffer fallback unchanged, including
when the SQLite query returns no rows

## Details
- add a byte-bounded `query_feedback_logs` helper in `codex-state` so
`/feedback` does not fetch all rows before truncating
- scope SQLite feedback logs to the requested thread plus threadless
rows from the same `process_uuid`
- format exported SQLite feedback lines with the log level prefix to
better match the in-memory feedback formatter
- add an explicit `LogDbLayer::flush()` control path and await it in
app-server before querying SQLite for feedback logs
- pass optional SQLite log bytes through `codex-feedback` as the
`codex-logs.log` attachment override
- leave TUI behavior unchanged apart from the updated `upload_feedback`
call signature
- add regression coverage for:
  - newest-within-budget ordering
  - excluding oversized newest rows
  - including same-process threadless rows
  - keeping the newest suffix across mixed thread and threadless rows
  - matching the feedback formatter shape aside from span prefixes
  - falling back to the in-memory snapshot when SQLite returns no logs
  - flushing buffered SQLite rows before querying

## Follow-up
- SQLite feedback exports still do not reproduce span prefixes like
`feedback-thread{thread_id=...}:`; there is a `TODO(ccunningham)` in
`codex-rs/state/src/log_db.rs` for that follow-up.

## Testing
- `cd codex-rs && cargo test -p codex-state`
- `cd codex-rs && cargo test -p codex-app-server`
- `cd codex-rs && just fmt`
2026-03-03 11:17:06 -08:00
pakrym-oai
69df12efb3 Remove Responses V1 websocket implementation (#13364)
V2 is the way to go!
2026-03-03 11:32:53 -07:00
Anton Panasenko
8da7e4bdae app-server-protocol: export flat v2 schema bundle (#13324)
## Summary
- add an `--experimental` flag to the export binary and thread the
option through TypeScript and JSON schema generation
- flatten the v2 schema bundle into a datamodel-code-generator-friendly
`codex_app_server_protocol.v2.schemas.json` export
- retarget shared helper refs to namespaced v2 definitions, add coverage
for the new export behavior, and vendor the generated schema fixtures

## Validation
- `cargo test -p codex-app-server-protocol` (71 unit tests and bin
targets passed locally; the final schema fixture integration target was
revalidated via fresh schema regeneration and a tree diff)
- `./target/debug/write_schema_fixtures --schema-root <tmpdir>`
- `diff -rq app-server-protocol/schema <tmpdir>`

## Tickets
- None
2026-03-03 10:25:51 -08:00
Ahmed Ibrahim
f6288248f4 realtime prompt changes (#13376)
# 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-03-03 10:13:14 -08:00
EFRAZER-oai
168e35b6f2 Add Windows direct install script (#12741)
## Summary
- add a direct install script for Windows at
`scripts/install/install.ps1`
- extend release staging so `install.ps1` is published alongside
`install.sh`
- install the Windows runtime payload (`codex.exe`, `rg.exe`, and helper
binaries) from the existing platform npm package

## Dependencies
- Depends on https://github.com/openai/codex/pull/12740

## Testing
- Smoke-tested with powershell
2026-03-03 09:25:50 -08:00
jif-oai
8159f05dfd feat: wire spreadsheet artifact (#13362) 2026-03-03 15:27:37 +00:00
jif-oai
24ba01b9da feat: artifact presentation part 7 (#13360) 2026-03-03 15:03:25 +00:00
jif-oai
1df040e62b feat: add multi-actions to presentation tool (#13357) 2026-03-03 14:37:26 +00:00
jif-oai
ad393fa753 feat: pres artifact part 5 (#13355)
Mostly written by Codex
2026-03-03 14:08:01 +00:00
jif-oai
821024f9c9 feat: spreadsheet part 3 (#13350)
=
2026-03-03 13:09:37 +00:00
jif-oai
a7d90b867d feat: presentation part 4 (#13348) 2026-03-03 12:51:31 +00:00
jif-oai
875eaac0d1 feat: spreadsheet v2 (#13347) 2026-03-03 12:38:27 +00:00
jif-oai
8c5e50ef39 feat: spreadsheet artifact (#13345) 2026-03-03 12:25:40 +00:00
jif-oai
564a883c2a feat: pres artifact 3 (#13346) 2026-03-03 12:18:25 +00:00
jif-oai
72dc444b2c feat: pres artifact 2 (#13344) 2026-03-03 12:00:34 +00:00
jif-oai
4874b9291a feat: presentation artifact p1 (#13341)
Part 1 of presentation tool artifact
2026-03-03 11:38:03 +00:00
pash-openai
07e532dcb9 app-server service tier plumbing (plus some cleanup) (#13334)
followup to https://github.com/openai/codex/pull/13212 to expose fast
tier controls to app server
(majority of this PR is generated schema jsons - actual code is +69 /
-35 and +24 tests )

- add service tier fields to the app-server protocol surfaces used by
thread lifecycle, turn start, config, and session configured events
- thread service tier through the app-server message processor and core
thread config snapshots
- allow runtime config overrides to carry service tier for app-server
callers

cleanup:
- Removing useless "legacy" code supporting "standard" - we moved to
None | "fast", so "standard" is not needed.
2026-03-03 02:35:09 -08:00
jif-oai
938c6dd388 fix: db windows path (#13336) 2026-03-03 09:50:52 +00:00
jif-oai
cacefb5228 fix: agent when profile (#13235)
Co-authored-by: Josh McKinney <joshka@openai.com>
Co-authored-by: Codex <noreply@openai.com>
2026-03-03 09:20:25 +00:00
jif-oai
3166a5ba82 fix: agent race (#13248)
https://github.com/openai/codex/issues/13244
2026-03-03 09:19:37 +00:00
bwanner-oai
6deb72c04b Renaming Team to Business plan during TUI onboarding (#13313)
Team is referred to as "Business"
2026-03-02 23:13:29 -08:00
Felipe Coury
745c48b088 fix(core): scope file search gitignore to repository context (#13250)
Closes #3493

## Problem

When a user's home directory (or any ancestor) contains a broad
`.gitignore` (e.g. `*` + `!.gitignore`), the `@` file mention picker in
Codex silently hides valid repository files like `package.json`. The
picker returns `no matches` for searches that should succeed. This is
surprising because manually typed paths still work, making the failure
hard to diagnose.

## Mental model

Git itself never walks above the repository root to assemble its ignore
list. Its `.gitignore` resolution is strictly scoped: it reads
`.gitignore` files from the repo root downward, the per-repo
`.git/info/exclude`, and the user's global excludes file (via
`core.excludesFile`). A `.gitignore` sitting in a parent directory above
the repo root has no effect on `git status`, `git ls-files`, or any
other git operation. Our file search should replicate this contract
exactly.

The `ignore` crate's `WalkBuilder` has a `require_git` flag that
controls whether it follows this contract:

- `require_git(false)` (the previous setting): the walker reads
`.gitignore` files from _all_ ancestor directories, even those above or
outside the repository root. This is a deliberate divergence from git's
behavior in the `ignore` crate, intended for non-git use cases. It means
a `~/.gitignore` with `*` will suppress every file in the walk—something
git itself would never do.

- `require_git(true)` (this fix): the walker only applies `.gitignore`
semantics when it detects a `.git` directory, scoping ignore resolution
to the repository boundary. This matches git's own behavior: parent
`.gitignore` files above the repo root have no effect.

The fix is a one-line change: `require_git(false)` becomes
`require_git(true)`.

## How `require_git(false)` got here

The setting was introduced in af338cc (#2981, "Improve @ file search:
include specific hidden dirs such as .github, .gitlab"). That PR's goal
was to make hidden directories like `.github` and `.vscode` discoverable
by setting `.hidden(false)` on the walker. The `require_git(false)` was
added alongside it with the comment _"Don't require git to be present to
apply git-related ignore rules"_—the author likely intended gitignore
rules to still filter results even when no `.git` directory exists (e.g.
searching an extracted tarball that has a `.gitignore` but no `.git`).

The unintended consequence: with `require_git(false)`, the `ignore`
crate walks _above_ the search root to find `.gitignore` files in
ancestor directories. This is a side effect the original author almost
certainly didn't anticipate. The PR message says "Preserve `.gitignore`
semantics," but `require_git(false)` actually _breaks_ git's semantics
by applying ancestor ignore files that git itself would never read.

In short: the intent was "apply gitignore even without `.git`" but the
effect was "apply gitignore from every ancestor directory." This fix
restores git-correct scoping.

## Non-goals

- This PR does not change behavior when `respect_gitignore` is `false`
(that path already disables all git-related ignore rules).
- The first test
(`parent_gitignore_outside_repo_does_not_hide_repo_files`) intentionally
omits `git init`. The `ignore` crate's `require_git(true)` causes it to
skip gitignore processing entirely when no `.git` exists, which is the
desired behavior for that scenario. A second test
(`git_repo_still_respects_local_gitignore_when_enabled`) covers the
complementary case with a real git repo.

## Tradeoffs

**Behavioral shift**: With `require_git(true)`, directories that contain
`.gitignore` files but are _not_ inside a git repository will no longer
have those ignore rules applied during `@` search. This is a correctness
improvement for the primary use case (searching inside repos), but
changes behavior for the edge case of searching non-repo directories
that happen to have `.gitignore` files. In practice, Codex is
overwhelmingly used inside git repositories, so this tradeoff strongly
favors the fix.

**Two test strategies**: The first test omits `git init` to verify
parent ignore leakage is blocked; the second runs `git init` to verify
the repo's own `.gitignore` is still honored. Together they cover both
sides of the `require_git(true)` contract.

## Architecture

The change is in `walker_worker()` within
`codex-rs/file-search/src/lib.rs`, which configures the
`ignore::WalkBuilder` used by the file search walker thread. The walker
feeds discovered file paths into `nucleo` for fuzzy matching. The
`require_git` flag controls whether the walker consults `.gitignore`
files at all—it sits upstream of all ignore processing.

```
walker_worker
  └─ WalkBuilder::new(root)
       ├─ .hidden(false)         — include dotfiles
       ├─ .follow_links(true)    — follow symlinks
       ├─ .require_git(true)     — ← THE FIX: only apply gitignore in git repos
       └─ (conditional) git_ignore(false), git_global(false), etc.
            └─ applied when respect_gitignore == false
```

## Tests

- `parent_gitignore_outside_repo_does_not_hide_repo_files`: creates a
temp directory tree with a parent `.gitignore` containing `*`, a child
"repo" directory with `package.json` and `.vscode/settings.json`, and
asserts that both files are discoverable via `run()` with
`respect_gitignore: true`.
- `git_repo_still_respects_local_gitignore_when_enabled`: the
complementary test—runs `git init` inside the child directory and
verifies that the repo's own `.gitignore` exclusions still work (e.g.
`.vscode/extensions.json` is excluded while `.vscode/settings.json` is
whitelisted). Confirms that `require_git(true)` does not disable
gitignore processing inside actual git repositories.
2026-03-02 21:52:20 -07:00
pash-openai
2f5b01abd6 add fast mode toggle (#13212)
- add a local Fast mode setting in codex-core (similar to how model id
is currently stored on disk locally)
- send `service_tier=priority` on requests when Fast is enabled
- add `/fast` in the TUI and persist it locally
- feature flag
2026-03-02 20:29:33 -08:00
won
70e92b2950 schema changes 2026-03-02 19:33:53 -08:00
won
2c85c2d890 input modality, swapped to under-dev and added some integration tests 2026-03-02 18:21:12 -08:00
rakan-oai
56cc2c71f4 tui: preserve kill buffer across submit and slash-command clears (#12006)
## Problem

Before this change, composer paths that cleared the textarea after
submit or slash-command dispatch
also cleared the textarea kill buffer. That meant a user could `Ctrl+K`
part of a draft, trigger a
composer action that cleared the visible draft, and then lose the
ability to `Ctrl+Y` the killed
text back.

This was especially awkward for workflows where the user wants to
temporarily remove text, run a
composer action such as changing reasoning level or dispatching a slash
command, and then restore
the killed text into the now-empty draft.

## Mental model

This change separates visible draft state from editing-history state.

The visible draft includes the current textarea contents and text
elements that should be cleared
when the composer submits or dispatches a command. The kill buffer is
different: it represents the
most recent killed text and should survive those composer-driven clears
so the user can still yank
it back afterward.

After this change, submit and slash-command dispatch still clear the
visible textarea contents, but
they no longer erase the most recent kill.

## Non-goals

This does not implement a multi-entry kill ring or change the semantics
of `Ctrl+K` and `Ctrl+Y`
beyond preserving the existing yank target across these clears.

It also does not change how submit, slash-command parsing, prompt
expansion, or attachment handling
work, except that those flows no longer discard the textarea kill buffer
as a side effect of
clearing the draft.

## Tradeoffs

The main tradeoff is that clearing the visible textarea is no longer
equivalent to fully resetting
all editing state. That is intentional here, because submit and
slash-command dispatch are composer
actions, not requests to forget the user's most recent kill.

The benefit is better editing continuity. The cost is that callers must
understand that full-buffer
replacement resets visible draft state but not the kill buffer.

## Architecture

The behavioral change is in `TextArea`: full-buffer replacement now
rebuilds text and elements
without clearing `kill_buffer`.

`ChatComposer` already clears the textarea after successful submit and
slash-command dispatch by
calling into those textarea replacement paths. With this change, those
existing composer flows
inherit the new behavior automatically: the visible draft is cleared,
but the last killed text
remains available for `Ctrl+Y`.

The tests cover both layers:

- `TextArea` verifies that the kill buffer survives full-buffer
replacement.
- `ChatComposer` verifies that it survives submit.
- `ChatComposer` also verifies that it survives slash-command dispatch.

## Observability

There is no dedicated logging for kill-buffer preservation. The most
direct way to reason about the
behavior is to inspect textarea-wide replacement paths and confirm
whether they treat the kill
buffer as visible-buffer state or as editing-history state.

If this regresses in the future, the likely failure mode is simple and
user-visible: `Ctrl+Y` stops
restoring text after submit or slash-command clears even though ordinary
kill/yank still works
within a single uninterrupted draft.

## Tests

Added focused regression coverage for the new contract:

- `kill_buffer_persists_across_set_text`
- `kill_buffer_persists_after_submit`
- `kill_buffer_persists_after_slash_command_dispatch`

Local verification:
- `just fmt`
- `cargo test -p codex-tui`

---------

Co-authored-by: Josh McKinney <joshka@openai.com>
2026-03-03 02:06:08 +00:00
Celia Chen
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
Owen Lin
9965bf31fa feat(app-server-test-client): support tracing (#13286) 2026-03-02 17:24:48 -08:00
Brian Fioca
50084339a6 Adjusting plan prompt for clarity and verbosity (#13284)
`plan.md` prompt changes to tighten plan clarity and verbosity.
2026-03-03 01:14:39 +00:00
Ruslan Nigmatullin
9022cdc563 app-server: Silence thread status changes caused by thread being created (#13079)
Currently we emit `thread/status/changed` with `Idle` status right
before sending `thread/started` event (which also has `Idle` status in
it).
It feels that there is no point in that as client has no way to know
prior state of the thread as it didn't exist yet, so silence these kinds
of notifications.
2026-03-03 00:52:28 +00:00
Owen Lin
146b798129 fix(app-server): emit turn/started only when turn actually starts (#13261)
This is a follow-up for https://github.com/openai/codex/pull/13047

## Why
We had a race where `turn/started` could be observed before the thread
had actually transitioned to `Active`. This was because we eagerly
emitted `turn/started` in the request handler for `turn/start` (and
`review/start`).

That was showing up as flaky `thread/resume` tests, but the real issue
was broader: a client could see `turn/started` and still get back an
idle thread immediately afterward.

The first idea was to eagerly call
`thread_watch_manager.note_turn_started(...)` from the `turn/start`
request path. That turns out to be unsafe, because
`submit(Op::UserInput)` only queues work. If a turn starts and completes
quickly, request-path bookkeeping can race with the real lifecycle
events and leave stale running state behind.

**The real fix** is to move `turn/started` to emit only after the turn
_actually_ starts, so we do that by waiting for the
`EventMsg::TurnStarted` notification emitted by codex core. We do this
for both `turn/start` and `review/start`.

I also verified this change is safe for our first-party codex apps -
they don't have any assumptions that `turn/started` is emitted before
the RPC response to `turn/start` (which is correct anyway).

I also removed `single_client_mode` since it isn't really necessary now.

## Testing
- `cargo test -p codex-app-server thread_resume -- --nocapture`
- `cargo test -p codex-app-server
'suite::v2::turn_start::turn_start_emits_notifications_and_accepts_model_override'
-- --exact --nocapture`
- `cargo test -p codex-app-server`
2026-03-02 16:43:31 -08:00
Ahmed Ibrahim
b20b6aa46f Update realtime websocket API (#13265)
- migrate the realtime websocket transport to the new session and
handoff flow
- make the realtime model configurable in config.toml and use API-key
auth for the websocket

---------

Co-authored-by: Codex <noreply@openai.com>
2026-03-02 16:05:40 -08:00
Owen Lin
d473e8d56d feat(app-server): add tracing to all app-server APIs (#13285)
### Overview
This PR adds the first piece of tracing for app-server JSON-RPC
requests.

There are two main changes:
- JSON-RPC requests can now take an optional W3C trace context at the
top level via a `trace` field (`traceparent` / `tracestate`).
- app-server now creates a dedicated request span for every inbound
JSON-RPC request in `MessageProcessor`, and uses the request-level trace
context as the parent when present.

For compatibility with existing flows, app-server still falls back to
the TRACEPARENT env var when there is no request-level traceparent.

This PR is intentionally scoped to the app-server boundary. In a
followup, we'll actually propagate trace context through the async
handoff into core execution spans like run_turn, which will make
app-server traces much more useful.

### Spans
A few details on the app-server span shape:
- each inbound request gets its own server span
- span/resource names are based on the JSON-RPC method (`initialize`,
`thread/start`, `turn/start`, etc.)
- spans record transport (stdio vs websocket), request id, connection
id, and client name/version when available
- `initialize` stores client metadata in session state so later requests
on the same connection can reuse it
2026-03-02 16:01:41 -08:00
Ruslan Nigmatullin
14fcb6645c app-server: Update thread/name/set to support not-loaded threads (#13282)
Currently `thread/name/set` does only work for loaded threads.
Expand the scope to also support persisted but not-yet-loaded ones for a
more predictable API surface.
This will make it possible to rename threads discovered via
`thread/list` and similar operations.
2026-03-02 15:13:18 -08:00
won
0310a658f0 Add experimental image generation foundation 2026-03-02 14:49:13 -08:00
Josh McKinney
75e7c804ea test(app-server): increase flow test timeout to reduce flake (#11814)
## Summary
- increase `DEFAULT_READ_TIMEOUT` in `codex_message_processor_flow` from
20s to 45s
- keep test behavior the same while avoiding platform timing flakes

## Why
Windows ARM64 CI showed these tests taking about 24s before
`task_complete`, which could fail early and produce wiremock
request-count mismatches.

## Testing
- just fmt
- cargo test -p codex-app-server codex_message_processor_flow --
--nocapture
2026-03-02 12:29:28 -08:00
Dylan Hurd
e10df4ba10 fix(core) shell_snapshot multiline exports (#12642)
## Summary
Codex discovered this one - shell_snapshot tests were breaking on my
machine because I had a multiline env var. We should handle these!

## Testing
- [x] existing tests pass
- [x] Updated unit tests
2026-03-02 12:08:17 -07:00
jif-oai
f8838fd6f3 feat: enable ma through /agent (#13246)
<img width="639" height="139" alt="Screenshot 2026-03-02 at 16 06 41"
src="https://github.com/user-attachments/assets/c006fcec-c1e7-41ce-bb84-c121d5ffb501"
/>

Then
<img width="372" height="37" alt="Screenshot 2026-03-02 at 16 06 49"
src="https://github.com/user-attachments/assets/aa4ad703-e7e7-4620-9032-f5cd4f48ff79"
/>
2026-03-02 18:37:29 +00:00
Charley Cunningham
7979ce453a tui: restore draft footer hints (#13202)
## Summary
- restore `Tab to queue` when a draft is present and the agent is
running
- keep draft-idle footers passive by showing the normal footer or status
line instead of `? for shortcuts`
- align footer snapshot coverage with the updated draft footer behavior

## Codex author
`codex resume 019c7f1c-43aa-73e0-97c7-40f457396bb0`

---------

Co-authored-by: Codex <noreply@openai.com>
2026-03-02 10:26:13 -08:00
Eric Traut
7709bf32a3 Fix project trust config parsing so CLI overrides work (#13090)
Fixes #13076

This PR fixes a bug that causes command-line config overrides for MCP
subtables to not be merged correctly.

Summary
- make project trust loading go through the dedicated struct so CLI
overrides can update trusted project-local MCP transports

---------

Co-authored-by: jif-oai <jif@openai.com>
2026-03-02 11:10:38 -07:00
Michael Bolin
3241c1c6cc fix: use https://git.savannah.gnu.org/git/bash instead of https://github.com/bolinfest/bash (#13057)
Historically, we cloned the Bash repo from
https://github.com/bminor/bash, but for whatever reason, it was removed
at some point.

I had a local clone of it, so I pushed it to
https://github.com/bolinfest/bash so that we could continue running our
CI job. I did this in https://github.com/openai/codex/pull/9563, and as
you can see, I did not tamper with the commit hash we used as the basis
of this build.

Using a personal fork is not great, so this PR changes the CI job to use
what appears to be considered the source of truth for Bash, which is
https://git.savannah.gnu.org/git/bash.git.

Though in testing this out, it appears this Git server does not support
the combination of `git clone --depth 1
https://git.savannah.gnu.org/git/bash` and `git fetch --depth 1 origin
a8a1c2fac029404d3f42cd39f5a20f24b6e4fe4b`, as it fails with the
following error:

```
error: Server does not allow request for unadvertised object a8a1c2fac029404d3f42cd39f5a20f24b6e4fe4b
```

so unfortunately this means that we have to do a full clone instead of a
shallow clone in our CI jobs, which will be a bit slower.

Also updated `codex-rs/shell-escalation/README.md` to reflect this
change.
2026-03-02 09:09:54 -08:00
354 changed files with 56330 additions and 8247 deletions

View File

@@ -335,7 +335,7 @@ jobs:
comment-on-issue:
name: Comment with potential duplicates
needs: select-final
if: ${{ needs.select-final.result != 'skipped' }}
if: ${{ always() && needs.select-final.result == 'success' }}
runs-on: ubuntu-latest
permissions:
contents: read

View File

@@ -494,9 +494,10 @@ jobs:
--package codex-responses-api-proxy \
--package codex-sdk
- name: Stage macOS and Linux installer script
- name: Stage installer scripts
run: |
cp scripts/install/install.sh dist/install.sh
cp scripts/install/install.ps1 dist/install.ps1
- name: Create GitHub Release
uses: softprops/action-gh-release@v2

View File

@@ -146,9 +146,8 @@ jobs:
shell: bash
run: |
set -euo pipefail
git clone --depth 1 https://github.com/bolinfest/bash /tmp/bash
git clone https://git.savannah.gnu.org/git/bash /tmp/bash
cd /tmp/bash
git fetch --depth 1 origin a8a1c2fac029404d3f42cd39f5a20f24b6e4fe4b
git checkout a8a1c2fac029404d3f42cd39f5a20f24b6e4fe4b
git apply "${GITHUB_WORKSPACE}/shell-tool-mcp/patches/bash-exec-wrapper.patch"
./configure --without-bash-malloc
@@ -188,9 +187,8 @@ jobs:
shell: bash
run: |
set -euo pipefail
git clone --depth 1 https://github.com/bolinfest/bash /tmp/bash
git clone https://git.savannah.gnu.org/git/bash /tmp/bash
cd /tmp/bash
git fetch --depth 1 origin a8a1c2fac029404d3f42cd39f5a20f24b6e4fe4b
git checkout a8a1c2fac029404d3f42cd39f5a20f24b6e4fe4b
git apply "${GITHUB_WORKSPACE}/shell-tool-mcp/patches/bash-exec-wrapper.patch"
./configure --without-bash-malloc

View File

@@ -1,7 +1,7 @@
{
"rust-analyzer.checkOnSave": true,
"rust-analyzer.check.command": "clippy",
"rust-analyzer.check.extraArgs": ["--all-features", "--tests"],
"rust-analyzer.check.extraArgs": ["--tests"],
"rust-analyzer.rustfmt.extraArgs": ["--config", "imports_granularity=Item"],
"rust-analyzer.cargo.targetDir": "${workspaceFolder}/codex-rs/target/rust-analyzer",
"[rust]": {

41
MODULE.bazel.lock generated

File diff suppressed because one or more lines are too long

655
codex-rs/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -33,6 +33,8 @@ members = [
"mcp-server",
"network-proxy",
"ollama",
"artifact-presentation",
"artifact-spreadsheet",
"process-hardening",
"protocol",
"rmcp-client",
@@ -109,6 +111,8 @@ codex-mcp-server = { path = "mcp-server" }
codex-network-proxy = { path = "network-proxy" }
codex-ollama = { path = "ollama" }
codex-otel = { path = "otel" }
codex-artifact-presentation = { path = "artifact-presentation" }
codex-artifact-spreadsheet = { path = "artifact-spreadsheet" }
codex-process-hardening = { path = "process-hardening" }
codex-protocol = { path = "protocol" }
codex-responses-api-proxy = { path = "responses-api-proxy" }
@@ -215,6 +219,7 @@ owo-colors = "4.3.0"
path-absolutize = "3.1.1"
pathdiff = "0.2"
portable-pty = "0.9.0"
ppt-rs = "0.2.6"
predicates = "3"
pretty_assertions = "1.4.1"
pulldown-cmark = "0.10"
@@ -349,6 +354,7 @@ ignored = [
"openssl-sys",
"codex-utils-readiness",
"codex-secrets",
"codex-artifact-spreadsheet"
]
[profile.release]

View File

@@ -20,6 +20,7 @@ codex-utils-absolute-path = { workspace = true }
schemars = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
serde_with = { workspace = true }
shlex = { workspace = true }
strum_macros = { workspace = true }
thiserror = { workspace = true }

View File

@@ -514,6 +514,16 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
]
},
"image_url": {
"type": "string"
},
@@ -627,6 +637,15 @@
],
"type": "string"
},
"ImageDetail": {
"enum": [
"auto",
"low",
"high",
"original"
],
"type": "string"
},
"InitializeCapabilities": {
"description": "Client-declared capabilities negotiated during initialize.",
"properties": {
@@ -1397,6 +1416,40 @@
"title": "WebSearchCallResponseItem",
"type": "object"
},
{
"properties": {
"id": {
"type": "string"
},
"result": {
"type": "string"
},
"revised_prompt": {
"type": [
"string",
"null"
]
},
"status": {
"type": "string"
},
"type": {
"enum": [
"image_generation_call"
],
"title": "ImageGenerationCallResponseItemType",
"type": "string"
}
},
"required": [
"id",
"result",
"status",
"type"
],
"title": "ImageGenerationCallResponseItem",
"type": "object"
},
{
"properties": {
"ghost_commit": {
@@ -1703,6 +1756,12 @@
}
]
},
"ServiceTier": {
"enum": [
"fast"
],
"type": "string"
},
"Settings": {
"description": "Settings for a collaboration mode.",
"properties": {
@@ -1933,6 +1992,23 @@
}
]
},
"serviceTier": {
"anyOf": [
{
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
]
},
{
"type": "null"
}
]
},
"threadId": {
"type": "string"
}
@@ -2036,6 +2112,54 @@
},
"type": "object"
},
"ThreadMetadataGitInfoUpdateParams": {
"properties": {
"branch": {
"description": "Omit to leave the stored branch unchanged, set to `null` to clear it, or provide a non-empty string to replace it.",
"type": [
"string",
"null"
]
},
"originUrl": {
"description": "Omit to leave the stored origin URL unchanged, set to `null` to clear it, or provide a non-empty string to replace it.",
"type": [
"string",
"null"
]
},
"sha": {
"description": "Omit to leave the stored commit unchanged, set to `null` to clear it, or provide a non-empty string to replace it.",
"type": [
"string",
"null"
]
}
},
"type": "object"
},
"ThreadMetadataUpdateParams": {
"properties": {
"gitInfo": {
"anyOf": [
{
"$ref": "#/definitions/ThreadMetadataGitInfoUpdateParams"
},
{
"type": "null"
}
],
"description": "Patch the stored Git metadata for this thread. Omit a field to leave it unchanged, set it to `null` to clear it, or provide a string to replace the stored value."
},
"threadId": {
"type": "string"
}
},
"required": [
"threadId"
],
"type": "object"
},
"ThreadReadParams": {
"properties": {
"includeTurns": {
@@ -2155,6 +2279,23 @@
}
]
},
"serviceTier": {
"anyOf": [
{
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
]
},
{
"type": "null"
}
]
},
"threadId": {
"type": "string"
}
@@ -2299,6 +2440,23 @@
"string",
"null"
]
},
"serviceTier": {
"anyOf": [
{
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
]
},
{
"type": "null"
}
]
}
},
"type": "object"
@@ -2409,6 +2567,24 @@
],
"description": "Override the sandbox policy for this turn and subsequent turns."
},
"serviceTier": {
"anyOf": [
{
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
]
},
{
"type": "null"
}
],
"description": "Override the service tier for this turn and subsequent turns."
},
"summary": {
"anyOf": [
{
@@ -2864,6 +3040,30 @@
"title": "Thread/name/setRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"thread/metadata/update"
],
"title": "Thread/metadata/updateRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/ThreadMetadataUpdateParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Thread/metadata/updateRequest",
"type": "object"
},
{
"properties": {
"id": {

View File

@@ -1138,6 +1138,16 @@
],
"description": "How to sandbox commands executed in the system"
},
"service_tier": {
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
]
},
"session_id": {
"$ref": "#/definitions/ThreadId"
},
@@ -3468,6 +3478,16 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
]
},
"image_url": {
"type": "string"
},
@@ -3559,6 +3579,15 @@
],
"type": "object"
},
"ImageDetail": {
"enum": [
"auto",
"low",
"high",
"original"
],
"type": "string"
},
"LocalShellAction": {
"oneOf": [
{
@@ -4234,8 +4263,14 @@
{
"additionalProperties": false,
"properties": {
"SessionCreated": {
"SessionUpdated": {
"properties": {
"instructions": {
"type": [
"string",
"null"
]
},
"session_id": {
"type": "string"
}
@@ -4246,27 +4281,6 @@
"type": "object"
}
},
"required": [
"SessionCreated"
],
"title": "SessionCreatedRealtimeEvent",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"SessionUpdated": {
"properties": {
"backend_prompt": {
"type": [
"string",
"null"
]
}
},
"type": "object"
}
},
"required": [
"SessionUpdated"
],
@@ -4297,6 +4311,40 @@
"title": "ConversationItemAddedRealtimeEvent",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"ConversationItemDone": {
"properties": {
"item_id": {
"type": "string"
}
},
"required": [
"item_id"
],
"type": "object"
}
},
"required": [
"ConversationItemDone"
],
"title": "ConversationItemDoneRealtimeEvent",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"HandoffRequested": {
"$ref": "#/definitions/RealtimeHandoffRequested"
}
},
"required": [
"HandoffRequested"
],
"title": "HandoffRequestedRealtimeEvent",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
@@ -4312,6 +4360,47 @@
}
]
},
"RealtimeHandoffMessage": {
"properties": {
"role": {
"type": "string"
},
"text": {
"type": "string"
}
},
"required": [
"role",
"text"
],
"type": "object"
},
"RealtimeHandoffRequested": {
"properties": {
"handoff_id": {
"type": "string"
},
"input_transcript": {
"type": "string"
},
"item_id": {
"type": "string"
},
"messages": {
"items": {
"$ref": "#/definitions/RealtimeHandoffMessage"
},
"type": "array"
}
},
"required": [
"handoff_id",
"input_transcript",
"item_id",
"messages"
],
"type": "object"
},
"ReasoningEffort": {
"description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning",
"enum": [
@@ -4879,6 +4968,40 @@
"title": "WebSearchCallResponseItem",
"type": "object"
},
{
"properties": {
"id": {
"type": "string"
},
"result": {
"type": "string"
},
"revised_prompt": {
"type": [
"string",
"null"
]
},
"status": {
"type": "string"
},
"type": {
"enum": [
"image_generation_call"
],
"title": "ImageGenerationCallResponseItemType",
"type": "string"
}
},
"required": [
"id",
"result",
"status",
"type"
],
"title": "ImageGenerationCallResponseItem",
"type": "object"
},
{
"properties": {
"ghost_commit": {
@@ -5350,6 +5473,12 @@
}
]
},
"ServiceTier": {
"enum": [
"fast"
],
"type": "string"
},
"SessionNetworkProxyRuntime": {
"properties": {
"admin_addr": {
@@ -6694,6 +6823,16 @@
],
"description": "How to sandbox commands executed in the system"
},
"service_tier": {
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
]
},
"session_id": {
"$ref": "#/definitions/ThreadId"
},

View File

@@ -70,7 +70,18 @@
"method": {
"type": "string"
},
"params": true
"params": true,
"trace": {
"anyOf": [
{
"$ref": "#/definitions/W3cTraceContext"
},
{
"type": "null"
}
],
"description": "Optional W3C Trace Context for distributed tracing."
}
},
"required": [
"id",
@@ -102,6 +113,23 @@
"type": "integer"
}
]
},
"W3cTraceContext": {
"properties": {
"traceparent": {
"type": [
"string",
"null"
]
},
"tracestate": {
"type": [
"string",
"null"
]
}
},
"type": "object"
}
},
"description": "Refers to any valid JSON-RPC object that can be decoded off the wire, or encoded to be sent.",

View File

@@ -11,6 +11,23 @@
"type": "integer"
}
]
},
"W3cTraceContext": {
"properties": {
"traceparent": {
"type": [
"string",
"null"
]
},
"tracestate": {
"type": [
"string",
"null"
]
}
},
"type": "object"
}
},
"description": "A request that expects a response.",
@@ -21,7 +38,18 @@
"method": {
"type": "string"
},
"params": true
"params": true,
"trace": {
"anyOf": [
{
"$ref": "#/definitions/W3cTraceContext"
},
{
"type": "null"
}
],
"description": "Optional W3C Trace Context for distributed tracing."
}
},
"required": [
"id",

View File

@@ -5,7 +5,7 @@
"properties": {
"read": {
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/v2/AbsolutePathBuf"
},
"type": [
"array",
@@ -14,7 +14,7 @@
},
"write": {
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/v2/AbsolutePathBuf"
},
"type": [
"array",
@@ -523,6 +523,30 @@
"title": "Thread/name/setRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/v2/RequestId"
},
"method": {
"enum": [
"thread/metadata/update"
],
"title": "Thread/metadata/updateRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/v2/ThreadMetadataUpdateParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Thread/metadata/updateRequest",
"type": "object"
},
{
"properties": {
"id": {
@@ -1382,7 +1406,7 @@
"thread_id": {
"allOf": [
{
"$ref": "#/definitions/ThreadId"
"$ref": "#/definitions/v2/ThreadId"
}
],
"description": "Thread ID of the receiver/new agent."
@@ -1420,7 +1444,7 @@
"thread_id": {
"allOf": [
{
"$ref": "#/definitions/ThreadId"
"$ref": "#/definitions/v2/ThreadId"
}
],
"description": "Thread ID of the receiver/new agent."
@@ -2306,6 +2330,16 @@
],
"description": "How to sandbox commands executed in the system"
},
"service_tier": {
"anyOf": [
{
"$ref": "#/definitions/v2/ServiceTier"
},
{
"type": "null"
}
]
},
"session_id": {
"$ref": "#/definitions/v2/ThreadId"
},
@@ -4722,7 +4756,7 @@
"properties": {
"read": {
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/v2/AbsolutePathBuf"
},
"type": [
"array",
@@ -4731,7 +4765,7 @@
},
"write": {
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
"$ref": "#/definitions/v2/AbsolutePathBuf"
},
"type": [
"array",
@@ -5016,7 +5050,18 @@
"method": {
"type": "string"
},
"params": true
"params": true,
"trace": {
"anyOf": [
{
"$ref": "#/definitions/W3cTraceContext"
},
{
"type": "null"
}
],
"description": "Optional W3C Trace Context for distributed tracing."
}
},
"required": [
"id",
@@ -5443,8 +5488,14 @@
{
"additionalProperties": false,
"properties": {
"SessionCreated": {
"SessionUpdated": {
"properties": {
"instructions": {
"type": [
"string",
"null"
]
},
"session_id": {
"type": "string"
}
@@ -5455,27 +5506,6 @@
"type": "object"
}
},
"required": [
"SessionCreated"
],
"title": "SessionCreatedRealtimeEvent",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"SessionUpdated": {
"properties": {
"backend_prompt": {
"type": [
"string",
"null"
]
}
},
"type": "object"
}
},
"required": [
"SessionUpdated"
],
@@ -5506,6 +5536,40 @@
"title": "ConversationItemAddedRealtimeEvent",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"ConversationItemDone": {
"properties": {
"item_id": {
"type": "string"
}
},
"required": [
"item_id"
],
"type": "object"
}
},
"required": [
"ConversationItemDone"
],
"title": "ConversationItemDoneRealtimeEvent",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"HandoffRequested": {
"$ref": "#/definitions/RealtimeHandoffRequested"
}
},
"required": [
"HandoffRequested"
],
"title": "HandoffRequestedRealtimeEvent",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
@@ -5521,6 +5585,47 @@
}
]
},
"RealtimeHandoffMessage": {
"properties": {
"role": {
"type": "string"
},
"text": {
"type": "string"
}
},
"required": [
"role",
"text"
],
"type": "object"
},
"RealtimeHandoffRequested": {
"properties": {
"handoff_id": {
"type": "string"
},
"input_transcript": {
"type": "string"
},
"item_id": {
"type": "string"
},
"messages": {
"items": {
"$ref": "#/definitions/RealtimeHandoffMessage"
},
"type": "array"
}
},
"required": [
"handoff_id",
"input_transcript",
"item_id",
"messages"
],
"type": "object"
},
"RejectConfig": {
"properties": {
"mcp_elicitations": {
@@ -7050,7 +7155,7 @@
"properties": {
"content": {
"items": {
"$ref": "#/definitions/UserInput"
"$ref": "#/definitions/v2/UserInput"
},
"type": "array"
},
@@ -7088,7 +7193,7 @@
"phase": {
"anyOf": [
{
"$ref": "#/definitions/MessagePhase"
"$ref": "#/definitions/v2/MessagePhase"
},
{
"type": "null"
@@ -7173,7 +7278,7 @@
{
"properties": {
"action": {
"$ref": "#/definitions/WebSearchAction"
"$ref": "#/definitions/v2/WebSearchAction"
},
"id": {
"type": "string"
@@ -7220,6 +7325,23 @@
}
]
},
"W3cTraceContext": {
"properties": {
"traceparent": {
"type": [
"string",
"null"
]
},
"tracestate": {
"type": [
"string",
"null"
]
}
},
"type": "object"
},
"v2": {
"AbsolutePathBuf": {
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
@@ -8507,6 +8629,16 @@
}
]
},
"service_tier": {
"anyOf": [
{
"$ref": "#/definitions/v2/ServiceTier"
},
{
"type": "null"
}
]
},
"tools": {
"anyOf": [
{
@@ -9568,6 +9700,16 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/v2/ImageDetail"
},
{
"type": "null"
}
]
},
"image_url": {
"type": "string"
},
@@ -9733,6 +9875,15 @@
],
"type": "string"
},
"ImageDetail": {
"enum": [
"auto",
"low",
"high",
"original"
],
"type": "string"
},
"InputModality": {
"description": "Canonical user-input modality tags advertised by a model.",
"oneOf": [
@@ -10757,6 +10908,16 @@
}
]
},
"service_tier": {
"anyOf": [
{
"$ref": "#/definitions/v2/ServiceTier"
},
{
"type": "null"
}
]
},
"web_search": {
"anyOf": [
{
@@ -11541,6 +11702,40 @@
"title": "WebSearchCallResponseItem",
"type": "object"
},
{
"properties": {
"id": {
"type": "string"
},
"result": {
"type": "string"
},
"revised_prompt": {
"type": [
"string",
"null"
]
},
"status": {
"type": "string"
},
"type": {
"enum": [
"image_generation_call"
],
"title": "ImageGenerationCallResponseItemType",
"type": "string"
}
},
"required": [
"id",
"result",
"status",
"type"
],
"title": "ImageGenerationCallResponseItem",
"type": "object"
},
{
"properties": {
"ghost_commit": {
@@ -11908,6 +12103,12 @@
"title": "ServerRequestResolvedNotification",
"type": "object"
},
"ServiceTier": {
"enum": [
"fast"
],
"type": "string"
},
"SessionSource": {
"oneOf": [
{
@@ -12723,6 +12924,23 @@
}
]
},
"serviceTier": {
"anyOf": [
{
"anyOf": [
{
"$ref": "#/definitions/v2/ServiceTier"
},
{
"type": "null"
}
]
},
{
"type": "null"
}
]
},
"threadId": {
"type": "string"
}
@@ -12761,6 +12979,16 @@
"sandbox": {
"$ref": "#/definitions/v2/SandboxPolicy"
},
"serviceTier": {
"anyOf": [
{
"$ref": "#/definitions/v2/ServiceTier"
},
{
"type": "null"
}
]
},
"thread": {
"$ref": "#/definitions/v2/Thread"
}
@@ -13460,6 +13688,69 @@
"title": "ThreadLoadedListResponse",
"type": "object"
},
"ThreadMetadataGitInfoUpdateParams": {
"properties": {
"branch": {
"description": "Omit to leave the stored branch unchanged, set to `null` to clear it, or provide a non-empty string to replace it.",
"type": [
"string",
"null"
]
},
"originUrl": {
"description": "Omit to leave the stored origin URL unchanged, set to `null` to clear it, or provide a non-empty string to replace it.",
"type": [
"string",
"null"
]
},
"sha": {
"description": "Omit to leave the stored commit unchanged, set to `null` to clear it, or provide a non-empty string to replace it.",
"type": [
"string",
"null"
]
}
},
"type": "object"
},
"ThreadMetadataUpdateParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"gitInfo": {
"anyOf": [
{
"$ref": "#/definitions/v2/ThreadMetadataGitInfoUpdateParams"
},
{
"type": "null"
}
],
"description": "Patch the stored Git metadata for this thread. Omit a field to leave it unchanged, set it to `null` to clear it, or provide a string to replace the stored value."
},
"threadId": {
"type": "string"
}
},
"required": [
"threadId"
],
"title": "ThreadMetadataUpdateParams",
"type": "object"
},
"ThreadMetadataUpdateResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"thread": {
"$ref": "#/definitions/v2/Thread"
}
},
"required": [
"thread"
],
"title": "ThreadMetadataUpdateResponse",
"type": "object"
},
"ThreadNameUpdatedNotification": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
@@ -13706,6 +13997,23 @@
}
]
},
"serviceTier": {
"anyOf": [
{
"anyOf": [
{
"$ref": "#/definitions/v2/ServiceTier"
},
{
"type": "null"
}
]
},
{
"type": "null"
}
]
},
"threadId": {
"type": "string"
}
@@ -13744,6 +14052,16 @@
"sandbox": {
"$ref": "#/definitions/v2/SandboxPolicy"
},
"serviceTier": {
"anyOf": [
{
"$ref": "#/definitions/v2/ServiceTier"
},
{
"type": "null"
}
]
},
"thread": {
"$ref": "#/definitions/v2/Thread"
}
@@ -13922,6 +14240,23 @@
"string",
"null"
]
},
"serviceTier": {
"anyOf": [
{
"anyOf": [
{
"$ref": "#/definitions/v2/ServiceTier"
},
{
"type": "null"
}
]
},
{
"type": "null"
}
]
}
},
"title": "ThreadStartParams",
@@ -13955,6 +14290,16 @@
"sandbox": {
"$ref": "#/definitions/v2/SandboxPolicy"
},
"serviceTier": {
"anyOf": [
{
"$ref": "#/definitions/v2/ServiceTier"
},
{
"type": "null"
}
]
},
"thread": {
"$ref": "#/definitions/v2/Thread"
}
@@ -14522,6 +14867,24 @@
],
"description": "Override the sandbox policy for this turn and subsequent turns."
},
"serviceTier": {
"anyOf": [
{
"anyOf": [
{
"$ref": "#/definitions/v2/ServiceTier"
},
{
"type": "null"
}
]
},
{
"type": "null"
}
],
"description": "Override the service tier for this turn and subsequent turns."
},
"summary": {
"anyOf": [
{

File diff suppressed because it is too large Load Diff

View File

@@ -323,6 +323,16 @@
}
]
},
"service_tier": {
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
]
},
"tools": {
"anyOf": [
{
@@ -608,6 +618,16 @@
}
]
},
"service_tier": {
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
]
},
"web_search": {
"anyOf": [
{
@@ -685,6 +705,12 @@
},
"type": "object"
},
"ServiceTier": {
"enum": [
"fast"
],
"type": "string"
},
"ToolsV2": {
"properties": {
"view_image": {

View File

@@ -103,6 +103,16 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
]
},
"image_url": {
"type": "string"
},
@@ -173,6 +183,15 @@
],
"type": "object"
},
"ImageDetail": {
"enum": [
"auto",
"low",
"high",
"original"
],
"type": "string"
},
"LocalShellAction": {
"oneOf": [
{
@@ -622,6 +641,40 @@
"title": "WebSearchCallResponseItem",
"type": "object"
},
{
"properties": {
"id": {
"type": "string"
},
"result": {
"type": "string"
},
"revised_prompt": {
"type": [
"string",
"null"
]
},
"status": {
"type": "string"
},
"type": {
"enum": [
"image_generation_call"
],
"title": "ImageGenerationCallResponseItemType",
"type": "string"
}
},
"required": [
"id",
"result",
"status",
"type"
],
"title": "ImageGenerationCallResponseItem",
"type": "object"
},
{
"properties": {
"ghost_commit": {

View File

@@ -50,6 +50,12 @@
"danger-full-access"
],
"type": "string"
},
"ServiceTier": {
"enum": [
"fast"
],
"type": "string"
}
},
"description": "There are two ways to fork a thread: 1. By thread_id: load the thread from disk by thread_id and fork it into a new thread. 2. By path: load the thread from disk by path and fork it into a new thread.\n\nIf using path, the thread_id param will be ignored.\n\nPrefer using thread_id whenever possible.",
@@ -112,6 +118,23 @@
}
]
},
"serviceTier": {
"anyOf": [
{
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
]
},
{
"type": "null"
}
]
},
"threadId": {
"type": "string"
}

View File

@@ -738,6 +738,12 @@
}
]
},
"ServiceTier": {
"enum": [
"fast"
],
"type": "string"
},
"SessionSource": {
"oneOf": [
{
@@ -1906,6 +1912,16 @@
"sandbox": {
"$ref": "#/definitions/SandboxPolicy"
},
"serviceTier": {
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
]
},
"thread": {
"$ref": "#/definitions/Thread"
}

View File

@@ -0,0 +1,52 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"ThreadMetadataGitInfoUpdateParams": {
"properties": {
"branch": {
"description": "Omit to leave the stored branch unchanged, set to `null` to clear it, or provide a non-empty string to replace it.",
"type": [
"string",
"null"
]
},
"originUrl": {
"description": "Omit to leave the stored origin URL unchanged, set to `null` to clear it, or provide a non-empty string to replace it.",
"type": [
"string",
"null"
]
},
"sha": {
"description": "Omit to leave the stored commit unchanged, set to `null` to clear it, or provide a non-empty string to replace it.",
"type": [
"string",
"null"
]
}
},
"type": "object"
}
},
"properties": {
"gitInfo": {
"anyOf": [
{
"$ref": "#/definitions/ThreadMetadataGitInfoUpdateParams"
},
{
"type": "null"
}
],
"description": "Patch the stored Git metadata for this thread. Omit a field to leave it unchanged, set it to `null` to clear it, or provide a string to replace the stored value."
},
"threadId": {
"type": "string"
}
},
"required": [
"threadId"
],
"title": "ThreadMetadataUpdateParams",
"type": "object"
}

File diff suppressed because it is too large Load Diff

View File

@@ -145,6 +145,16 @@
},
{
"properties": {
"detail": {
"anyOf": [
{
"$ref": "#/definitions/ImageDetail"
},
{
"type": "null"
}
]
},
"image_url": {
"type": "string"
},
@@ -215,6 +225,15 @@
],
"type": "object"
},
"ImageDetail": {
"enum": [
"auto",
"low",
"high",
"original"
],
"type": "string"
},
"LocalShellAction": {
"oneOf": [
{
@@ -672,6 +691,40 @@
"title": "WebSearchCallResponseItem",
"type": "object"
},
{
"properties": {
"id": {
"type": "string"
},
"result": {
"type": "string"
},
"revised_prompt": {
"type": [
"string",
"null"
]
},
"status": {
"type": "string"
},
"type": {
"enum": [
"image_generation_call"
],
"title": "ImageGenerationCallResponseItemType",
"type": "string"
}
},
"required": [
"id",
"result",
"status",
"type"
],
"title": "ImageGenerationCallResponseItem",
"type": "object"
},
{
"properties": {
"ghost_commit": {
@@ -738,6 +791,12 @@
],
"type": "string"
},
"ServiceTier": {
"enum": [
"fast"
],
"type": "string"
},
"WebSearchAction": {
"oneOf": [
{
@@ -910,6 +969,23 @@
}
]
},
"serviceTier": {
"anyOf": [
{
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
]
},
{
"type": "null"
}
]
},
"threadId": {
"type": "string"
}

View File

@@ -738,6 +738,12 @@
}
]
},
"ServiceTier": {
"enum": [
"fast"
],
"type": "string"
},
"SessionSource": {
"oneOf": [
{
@@ -1906,6 +1912,16 @@
"sandbox": {
"$ref": "#/definitions/SandboxPolicy"
},
"serviceTier": {
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
]
},
"thread": {
"$ref": "#/definitions/Thread"
}

View File

@@ -75,6 +75,12 @@
"danger-full-access"
],
"type": "string"
},
"ServiceTier": {
"enum": [
"fast"
],
"type": "string"
}
},
"properties": {
@@ -156,6 +162,23 @@
"string",
"null"
]
},
"serviceTier": {
"anyOf": [
{
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
]
},
{
"type": "null"
}
]
}
},
"title": "ThreadStartParams",

View File

@@ -738,6 +738,12 @@
}
]
},
"ServiceTier": {
"enum": [
"fast"
],
"type": "string"
},
"SessionSource": {
"oneOf": [
{
@@ -1906,6 +1912,16 @@
"sandbox": {
"$ref": "#/definitions/SandboxPolicy"
},
"serviceTier": {
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
]
},
"thread": {
"$ref": "#/definitions/Thread"
}

View File

@@ -299,6 +299,12 @@
}
]
},
"ServiceTier": {
"enum": [
"fast"
],
"type": "string"
},
"Settings": {
"description": "Settings for a collaboration mode.",
"properties": {
@@ -539,6 +545,24 @@
],
"description": "Override the sandbox policy for this turn and subsequent turns."
},
"serviceTier": {
"anyOf": [
{
"anyOf": [
{
"$ref": "#/definitions/ServiceTier"
},
{
"type": "null"
}
]
},
{
"type": "null"
}
],
"description": "Override the service tier for this turn and subsequent turns."
},
"summary": {
"anyOf": [
{

View File

@@ -1,6 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ThreadId } from "./ThreadId";
export type AddConversationListenerParams = { conversationId: ThreadId, experimentalRawEvents: boolean, };

View File

@@ -1,6 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ThreadId } from "./ThreadId";
export type ArchiveConversationParams = { conversationId: ThreadId, rolloutPath: string, };

View File

@@ -1,5 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type ArchiveConversationResponse = Record<string, never>;

View File

@@ -1,9 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AuthMode } from "./AuthMode";
/**
* Deprecated notification. Use AccountUpdatedNotification instead.
*/
export type AuthStatusChangeNotification = { authMethod: AuthMode | null, };

View File

@@ -1,5 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type CancelLoginChatGptResponse = Record<string, never>;

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { SandboxPolicy } from "./SandboxPolicy";
export type ExecOneOffCommandParams = { command: Array<string>, timeoutMs: bigint | null, cwd: string | null, sandboxPolicy: SandboxPolicy | null, };

View File

@@ -1,5 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type ExecOneOffCommandResponse = { exitCode: number, stdout: string, stderr: string, };

View File

@@ -1,7 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { NewConversationParams } from "./NewConversationParams";
import type { ThreadId } from "./ThreadId";
export type ForkConversationParams = { path: string | null, conversationId: ThreadId | null, overrides: NewConversationParams | null, };

View File

@@ -1,7 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { EventMsg } from "./EventMsg";
import type { ThreadId } from "./ThreadId";
export type ForkConversationResponse = { conversationId: ThreadId, model: string, initialMessages: Array<EventMsg> | null, rolloutPath: string, };

View File

@@ -1,9 +1,10 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ImageDetail } from "./ImageDetail";
/**
* Responses API compatible content items that can be returned by a tool call.
* This is a subset of ContentItem with the types we support as function call outputs.
*/
export type FunctionCallOutputContentItem = { "type": "input_text", text: string, } | { "type": "input_image", image_url: string, };
export type FunctionCallOutputContentItem = { "type": "input_text", text: string, } | { "type": "input_image", image_url: string, detail?: ImageDetail, };

View File

@@ -1,6 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { UserSavedConfig } from "./UserSavedConfig";
export type GetUserSavedConfigResponse = { config: UserSavedConfig, };

View File

@@ -2,4 +2,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type CancelLoginChatGptParams = { loginId: string, };
export type ImageDetail = "auto" | "low" | "high" | "original";

View File

@@ -1,10 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { TextElement } from "./TextElement";
export type InputItem = { "type": "text", "data": { text: string,
/**
* UI-defined spans within `text` used to render or persist special elements.
*/
text_elements: Array<TextElement>, } } | { "type": "image", "data": { image_url: string, } } | { "type": "localImage", "data": { path: string, } };

View File

@@ -1,6 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ThreadId } from "./ThreadId";
export type InterruptConversationParams = { conversationId: ThreadId, };

View File

@@ -1,6 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { TurnAbortReason } from "./TurnAbortReason";
export type InterruptConversationResponse = { abortReason: TurnAbortReason, };

View File

@@ -1,5 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type ListConversationsParams = { pageSize: number | null, cursor: string | null, modelProviders: Array<string> | null, };

View File

@@ -1,6 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ConversationSummary } from "./ConversationSummary";
export type ListConversationsResponse = { items: Array<ConversationSummary>, nextCursor: string | null, };

View File

@@ -1,5 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type LoginApiKeyResponse = Record<string, never>;

View File

@@ -1,8 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
/**
* Deprecated in favor of AccountLoginCompletedNotification.
*/
export type LoginChatGptCompleteNotification = { loginId: string, success: boolean, error: string | null, };

View File

@@ -1,5 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type LoginChatGptResponse = { loginId: string, authUrl: string, };

View File

@@ -1,5 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type LogoutChatGptResponse = Record<string, never>;

View File

@@ -1,8 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AskForApproval } from "./AskForApproval";
import type { SandboxMode } from "./SandboxMode";
import type { JsonValue } from "./serde_json/JsonValue";
export type NewConversationParams = { model: string | null, modelProvider: string | null, profile: string | null, cwd: string | null, approvalPolicy: AskForApproval | null, sandbox: SandboxMode | null, config: { [key in string]?: JsonValue } | null, baseInstructions: string | null, developerInstructions: string | null, compactPrompt: string | null, includeApplyPatchTool: boolean | null, };

View File

@@ -1,7 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ReasoningEffort } from "./ReasoningEffort";
import type { ThreadId } from "./ThreadId";
export type NewConversationResponse = { conversationId: ThreadId, model: string, reasoningEffort: ReasoningEffort | null, rolloutPath: string, };

View File

@@ -1,9 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AskForApproval } from "./AskForApproval";
import type { ReasoningEffort } from "./ReasoningEffort";
import type { ReasoningSummary } from "./ReasoningSummary";
import type { Verbosity } from "./Verbosity";
export type Profile = { model: string | null, modelProvider: string | null, approvalPolicy: AskForApproval | null, modelReasoningEffort: ReasoningEffort | null, modelReasoningSummary: ReasoningSummary | null, modelVerbosity: Verbosity | null, chatgptBaseUrl: string | null, };

View File

@@ -2,6 +2,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { RealtimeAudioFrame } from "./RealtimeAudioFrame";
import type { RealtimeHandoffRequested } from "./RealtimeHandoffRequested";
import type { JsonValue } from "./serde_json/JsonValue";
export type RealtimeEvent = { "SessionCreated": { session_id: string, } } | { "SessionUpdated": { backend_prompt: string | null, } } | { "AudioOut": RealtimeAudioFrame } | { "ConversationItemAdded": JsonValue } | { "Error": string };
export type RealtimeEvent = { "SessionUpdated": { session_id: string, instructions: string | null, } } | { "AudioOut": RealtimeAudioFrame } | { "ConversationItemAdded": JsonValue } | { "ConversationItemDone": { item_id: string, } } | { "HandoffRequested": RealtimeHandoffRequested } | { "Error": string };

View File

@@ -2,4 +2,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type GetUserAgentResponse = { userAgent: string, };
export type RealtimeHandoffMessage = { role: string, text: string, };

View File

@@ -0,0 +1,6 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { RealtimeHandoffMessage } from "./RealtimeHandoffMessage";
export type RealtimeHandoffRequested = { handoff_id: string, item_id: string, input_transcript: string, messages: Array<RealtimeHandoffMessage>, };

View File

@@ -1,5 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type RemoveConversationListenerParams = { subscriptionId: string, };

View File

@@ -1,5 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type RemoveConversationSubscriptionResponse = Record<string, never>;

View File

@@ -15,4 +15,4 @@ export type ResponseItem = { "type": "message", role: string, content: Array<Con
/**
* Set when using the Responses API.
*/
call_id: string | null, status: LocalShellStatus, action: LocalShellAction, } | { "type": "function_call", name: string, arguments: string, call_id: string, } | { "type": "function_call_output", call_id: string, output: FunctionCallOutputPayload, } | { "type": "custom_tool_call", status?: string, call_id: string, name: string, input: string, } | { "type": "custom_tool_call_output", call_id: string, output: FunctionCallOutputPayload, } | { "type": "web_search_call", status?: string, action?: WebSearchAction, } | { "type": "ghost_snapshot", ghost_commit: GhostCommit, } | { "type": "compaction", encrypted_content: string, } | { "type": "other" };
call_id: string | null, status: LocalShellStatus, action: LocalShellAction, } | { "type": "function_call", name: string, arguments: string, call_id: string, } | { "type": "function_call_output", call_id: string, output: FunctionCallOutputPayload, } | { "type": "custom_tool_call", status?: string, call_id: string, name: string, input: string, } | { "type": "custom_tool_call_output", call_id: string, output: FunctionCallOutputPayload, } | { "type": "web_search_call", status?: string, action?: WebSearchAction, } | { "type": "image_generation_call", id: string, status: string, revised_prompt?: string, result: string, } | { "type": "ghost_snapshot", ghost_commit: GhostCommit, } | { "type": "compaction", encrypted_content: string, } | { "type": "other" };

View File

@@ -1,8 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { NewConversationParams } from "./NewConversationParams";
import type { ResponseItem } from "./ResponseItem";
import type { ThreadId } from "./ThreadId";
export type ResumeConversationParams = { path: string | null, conversationId: ThreadId | null, history: Array<ResponseItem> | null, overrides: NewConversationParams | null, };

View File

@@ -1,7 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { EventMsg } from "./EventMsg";
import type { ThreadId } from "./ThreadId";
export type ResumeConversationResponse = { conversationId: ThreadId, model: string, initialMessages: Array<EventMsg> | null, rolloutPath: string, };

View File

@@ -1,5 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type SandboxMode = "read-only" | "workspace-write" | "danger-full-access";

View File

@@ -1,6 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AbsolutePathBuf } from "./AbsolutePathBuf";
export type SandboxSettings = { writableRoots: Array<AbsolutePathBuf>, networkAccess: boolean | null, excludeTmpdirEnvVar: boolean | null, excludeSlashTmp: boolean | null, };

View File

@@ -1,7 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { InputItem } from "./InputItem";
import type { ThreadId } from "./ThreadId";
export type SendUserMessageParams = { conversationId: ThreadId, items: Array<InputItem>, };

View File

@@ -1,5 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type SendUserMessageResponse = Record<string, never>;

View File

@@ -1,16 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AskForApproval } from "./AskForApproval";
import type { InputItem } from "./InputItem";
import type { ReasoningEffort } from "./ReasoningEffort";
import type { ReasoningSummary } from "./ReasoningSummary";
import type { SandboxPolicy } from "./SandboxPolicy";
import type { ThreadId } from "./ThreadId";
import type { JsonValue } from "./serde_json/JsonValue";
export type SendUserTurnParams = { conversationId: ThreadId, items: Array<InputItem>, cwd: string, approvalPolicy: AskForApproval, sandboxPolicy: SandboxPolicy, model: string, effort: ReasoningEffort | null, summary: ReasoningSummary,
/**
* Optional JSON Schema used to constrain the final assistant message for this turn.
*/
outputSchema: JsonValue | null, };

View File

@@ -1,5 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type SendUserTurnResponse = Record<string, never>;

View File

@@ -1,11 +1,8 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AuthStatusChangeNotification } from "./AuthStatusChangeNotification";
import type { FuzzyFileSearchSessionCompletedNotification } from "./FuzzyFileSearchSessionCompletedNotification";
import type { FuzzyFileSearchSessionUpdatedNotification } from "./FuzzyFileSearchSessionUpdatedNotification";
import type { LoginChatGptCompleteNotification } from "./LoginChatGptCompleteNotification";
import type { SessionConfiguredNotification } from "./SessionConfiguredNotification";
import type { AccountLoginCompletedNotification } from "./v2/AccountLoginCompletedNotification";
import type { AccountRateLimitsUpdatedNotification } from "./v2/AccountRateLimitsUpdatedNotification";
import type { AccountUpdatedNotification } from "./v2/AccountUpdatedNotification";
@@ -51,4 +48,4 @@ import type { WindowsWorldWritableWarningNotification } from "./v2/WindowsWorldW
/**
* Notification sent from the server to the client.
*/
export type ServerNotification = { "method": "error", "params": ErrorNotification } | { "method": "thread/started", "params": ThreadStartedNotification } | { "method": "thread/status/changed", "params": ThreadStatusChangedNotification } | { "method": "thread/archived", "params": ThreadArchivedNotification } | { "method": "thread/unarchived", "params": ThreadUnarchivedNotification } | { "method": "thread/closed", "params": ThreadClosedNotification } | { "method": "thread/name/updated", "params": ThreadNameUpdatedNotification } | { "method": "thread/tokenUsage/updated", "params": ThreadTokenUsageUpdatedNotification } | { "method": "turn/started", "params": TurnStartedNotification } | { "method": "turn/completed", "params": TurnCompletedNotification } | { "method": "turn/diff/updated", "params": TurnDiffUpdatedNotification } | { "method": "turn/plan/updated", "params": TurnPlanUpdatedNotification } | { "method": "item/started", "params": ItemStartedNotification } | { "method": "item/completed", "params": ItemCompletedNotification } | { "method": "rawResponseItem/completed", "params": RawResponseItemCompletedNotification } | { "method": "item/agentMessage/delta", "params": AgentMessageDeltaNotification } | { "method": "item/plan/delta", "params": PlanDeltaNotification } | { "method": "item/commandExecution/outputDelta", "params": CommandExecutionOutputDeltaNotification } | { "method": "item/commandExecution/terminalInteraction", "params": TerminalInteractionNotification } | { "method": "item/fileChange/outputDelta", "params": FileChangeOutputDeltaNotification } | { "method": "serverRequest/resolved", "params": ServerRequestResolvedNotification } | { "method": "item/mcpToolCall/progress", "params": McpToolCallProgressNotification } | { "method": "mcpServer/oauthLogin/completed", "params": McpServerOauthLoginCompletedNotification } | { "method": "account/updated", "params": AccountUpdatedNotification } | { "method": "account/rateLimits/updated", "params": AccountRateLimitsUpdatedNotification } | { "method": "app/list/updated", "params": AppListUpdatedNotification } | { "method": "item/reasoning/summaryTextDelta", "params": ReasoningSummaryTextDeltaNotification } | { "method": "item/reasoning/summaryPartAdded", "params": ReasoningSummaryPartAddedNotification } | { "method": "item/reasoning/textDelta", "params": ReasoningTextDeltaNotification } | { "method": "thread/compacted", "params": ContextCompactedNotification } | { "method": "model/rerouted", "params": ModelReroutedNotification } | { "method": "deprecationNotice", "params": DeprecationNoticeNotification } | { "method": "configWarning", "params": ConfigWarningNotification } | { "method": "fuzzyFileSearch/sessionUpdated", "params": FuzzyFileSearchSessionUpdatedNotification } | { "method": "fuzzyFileSearch/sessionCompleted", "params": FuzzyFileSearchSessionCompletedNotification } | { "method": "thread/realtime/started", "params": ThreadRealtimeStartedNotification } | { "method": "thread/realtime/itemAdded", "params": ThreadRealtimeItemAddedNotification } | { "method": "thread/realtime/outputAudio/delta", "params": ThreadRealtimeOutputAudioDeltaNotification } | { "method": "thread/realtime/error", "params": ThreadRealtimeErrorNotification } | { "method": "thread/realtime/closed", "params": ThreadRealtimeClosedNotification } | { "method": "windows/worldWritableWarning", "params": WindowsWorldWritableWarningNotification } | { "method": "windowsSandbox/setupCompleted", "params": WindowsSandboxSetupCompletedNotification } | { "method": "account/login/completed", "params": AccountLoginCompletedNotification } | { "method": "authStatusChange", "params": AuthStatusChangeNotification } | { "method": "loginChatGptComplete", "params": LoginChatGptCompleteNotification } | { "method": "sessionConfigured", "params": SessionConfiguredNotification };
export type ServerNotification = { "method": "error", "params": ErrorNotification } | { "method": "thread/started", "params": ThreadStartedNotification } | { "method": "thread/status/changed", "params": ThreadStatusChangedNotification } | { "method": "thread/archived", "params": ThreadArchivedNotification } | { "method": "thread/unarchived", "params": ThreadUnarchivedNotification } | { "method": "thread/closed", "params": ThreadClosedNotification } | { "method": "thread/name/updated", "params": ThreadNameUpdatedNotification } | { "method": "thread/tokenUsage/updated", "params": ThreadTokenUsageUpdatedNotification } | { "method": "turn/started", "params": TurnStartedNotification } | { "method": "turn/completed", "params": TurnCompletedNotification } | { "method": "turn/diff/updated", "params": TurnDiffUpdatedNotification } | { "method": "turn/plan/updated", "params": TurnPlanUpdatedNotification } | { "method": "item/started", "params": ItemStartedNotification } | { "method": "item/completed", "params": ItemCompletedNotification } | { "method": "rawResponseItem/completed", "params": RawResponseItemCompletedNotification } | { "method": "item/agentMessage/delta", "params": AgentMessageDeltaNotification } | { "method": "item/plan/delta", "params": PlanDeltaNotification } | { "method": "item/commandExecution/outputDelta", "params": CommandExecutionOutputDeltaNotification } | { "method": "item/commandExecution/terminalInteraction", "params": TerminalInteractionNotification } | { "method": "item/fileChange/outputDelta", "params": FileChangeOutputDeltaNotification } | { "method": "serverRequest/resolved", "params": ServerRequestResolvedNotification } | { "method": "item/mcpToolCall/progress", "params": McpToolCallProgressNotification } | { "method": "mcpServer/oauthLogin/completed", "params": McpServerOauthLoginCompletedNotification } | { "method": "account/updated", "params": AccountUpdatedNotification } | { "method": "account/rateLimits/updated", "params": AccountRateLimitsUpdatedNotification } | { "method": "app/list/updated", "params": AppListUpdatedNotification } | { "method": "item/reasoning/summaryTextDelta", "params": ReasoningSummaryTextDeltaNotification } | { "method": "item/reasoning/summaryPartAdded", "params": ReasoningSummaryPartAddedNotification } | { "method": "item/reasoning/textDelta", "params": ReasoningTextDeltaNotification } | { "method": "thread/compacted", "params": ContextCompactedNotification } | { "method": "model/rerouted", "params": ModelReroutedNotification } | { "method": "deprecationNotice", "params": DeprecationNoticeNotification } | { "method": "configWarning", "params": ConfigWarningNotification } | { "method": "fuzzyFileSearch/sessionUpdated", "params": FuzzyFileSearchSessionUpdatedNotification } | { "method": "fuzzyFileSearch/sessionCompleted", "params": FuzzyFileSearchSessionCompletedNotification } | { "method": "thread/realtime/started", "params": ThreadRealtimeStartedNotification } | { "method": "thread/realtime/itemAdded", "params": ThreadRealtimeItemAddedNotification } | { "method": "thread/realtime/outputAudio/delta", "params": ThreadRealtimeOutputAudioDeltaNotification } | { "method": "thread/realtime/error", "params": ThreadRealtimeErrorNotification } | { "method": "thread/realtime/closed", "params": ThreadRealtimeClosedNotification } | { "method": "windows/worldWritableWarning", "params": WindowsWorldWritableWarningNotification } | { "method": "windowsSandbox/setupCompleted", "params": WindowsSandboxSetupCompletedNotification } | { "method": "account/login/completed", "params": AccountLoginCompletedNotification };

View File

@@ -2,4 +2,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type LoginApiKeyParams = { apiKey: string, };
export type ServiceTier = "fast";

View File

@@ -5,6 +5,7 @@ import type { AskForApproval } from "./AskForApproval";
import type { EventMsg } from "./EventMsg";
import type { ReasoningEffort } from "./ReasoningEffort";
import type { SandboxPolicy } from "./SandboxPolicy";
import type { ServiceTier } from "./ServiceTier";
import type { SessionNetworkProxyRuntime } from "./SessionNetworkProxyRuntime";
import type { ThreadId } from "./ThreadId";
@@ -16,7 +17,7 @@ thread_name?: string,
/**
* Tell the client what model is being queried.
*/
model: string, model_provider_id: string,
model: string, model_provider_id: string, service_tier: ServiceTier | null,
/**
* When to escalate for approval for execution
*/

View File

@@ -1,8 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { EventMsg } from "./EventMsg";
import type { ReasoningEffort } from "./ReasoningEffort";
import type { ThreadId } from "./ThreadId";
export type SessionConfiguredNotification = { sessionId: ThreadId, model: string, reasoningEffort: ReasoningEffort | null, historyLogId: bigint, historyEntryCount: number, initialMessages: Array<EventMsg> | null, rolloutPath: string, };

View File

@@ -1,6 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ReasoningEffort } from "./ReasoningEffort";
export type SetDefaultModelParams = { model: string | null, reasoningEffort: ReasoningEffort | null, };

View File

@@ -1,5 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type SetDefaultModelResponse = Record<string, never>;

View File

@@ -7,7 +7,7 @@ export type TextElement = {
/**
* Byte range in the parent `text` buffer that this element occupies.
*/
byteRange: ByteRange,
byte_range: ByteRange,
/**
* Optional human-readable placeholder for the element, displayed in the UI.
*/

View File

@@ -1,5 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type Tools = { webSearch: boolean | null, viewImage: boolean | null, };

View File

@@ -1,5 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type UserInfoResponse = { allegedUserEmail: string | null, };

View File

@@ -1,14 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AskForApproval } from "./AskForApproval";
import type { ForcedLoginMethod } from "./ForcedLoginMethod";
import type { Profile } from "./Profile";
import type { ReasoningEffort } from "./ReasoningEffort";
import type { ReasoningSummary } from "./ReasoningSummary";
import type { SandboxMode } from "./SandboxMode";
import type { SandboxSettings } from "./SandboxSettings";
import type { Tools } from "./Tools";
import type { Verbosity } from "./Verbosity";
export type UserSavedConfig = { approvalPolicy: AskForApproval | null, sandboxMode: SandboxMode | null, sandboxSettings: SandboxSettings | null, forcedChatgptWorkspaceId: string | null, forcedLoginMethod: ForcedLoginMethod | null, model: string | null, modelReasoningEffort: ReasoningEffort | null, modelReasoningSummary: ReasoningSummary | null, modelVerbosity: Verbosity | null, tools: Tools | null, profile: string | null, profiles: { [key in string]?: Profile }, };

View File

@@ -1,8 +1,6 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
export type { AbsolutePathBuf } from "./AbsolutePathBuf";
export type { AddConversationListenerParams } from "./AddConversationListenerParams";
export type { AddConversationSubscriptionResponse } from "./AddConversationSubscriptionResponse";
export type { AgentMessageContent } from "./AgentMessageContent";
export type { AgentMessageContentDeltaEvent } from "./AgentMessageContentDeltaEvent";
export type { AgentMessageDeltaEvent } from "./AgentMessageDeltaEvent";
@@ -17,16 +15,11 @@ export type { AgentStatus } from "./AgentStatus";
export type { ApplyPatchApprovalParams } from "./ApplyPatchApprovalParams";
export type { ApplyPatchApprovalRequestEvent } from "./ApplyPatchApprovalRequestEvent";
export type { ApplyPatchApprovalResponse } from "./ApplyPatchApprovalResponse";
export type { ArchiveConversationParams } from "./ArchiveConversationParams";
export type { ArchiveConversationResponse } from "./ArchiveConversationResponse";
export type { AskForApproval } from "./AskForApproval";
export type { AuthMode } from "./AuthMode";
export type { AuthStatusChangeNotification } from "./AuthStatusChangeNotification";
export type { BackgroundEventEvent } from "./BackgroundEventEvent";
export type { ByteRange } from "./ByteRange";
export type { CallToolResult } from "./CallToolResult";
export type { CancelLoginChatGptParams } from "./CancelLoginChatGptParams";
export type { CancelLoginChatGptResponse } from "./CancelLoginChatGptResponse";
export type { ClientInfo } from "./ClientInfo";
export type { ClientNotification } from "./ClientNotification";
export type { ClientRequest } from "./ClientRequest";
@@ -66,16 +59,12 @@ export type { ExecCommandEndEvent } from "./ExecCommandEndEvent";
export type { ExecCommandOutputDeltaEvent } from "./ExecCommandOutputDeltaEvent";
export type { ExecCommandSource } from "./ExecCommandSource";
export type { ExecCommandStatus } from "./ExecCommandStatus";
export type { ExecOneOffCommandParams } from "./ExecOneOffCommandParams";
export type { ExecOneOffCommandResponse } from "./ExecOneOffCommandResponse";
export type { ExecOutputStream } from "./ExecOutputStream";
export type { ExecPolicyAmendment } from "./ExecPolicyAmendment";
export type { ExitedReviewModeEvent } from "./ExitedReviewModeEvent";
export type { FileChange } from "./FileChange";
export type { FileSystemPermissions } from "./FileSystemPermissions";
export type { ForcedLoginMethod } from "./ForcedLoginMethod";
export type { ForkConversationParams } from "./ForkConversationParams";
export type { ForkConversationResponse } from "./ForkConversationResponse";
export type { FunctionCallOutputBody } from "./FunctionCallOutputBody";
export type { FunctionCallOutputContentItem } from "./FunctionCallOutputContentItem";
export type { FunctionCallOutputPayload } from "./FunctionCallOutputPayload";
@@ -89,35 +78,24 @@ export type { GetAuthStatusResponse } from "./GetAuthStatusResponse";
export type { GetConversationSummaryParams } from "./GetConversationSummaryParams";
export type { GetConversationSummaryResponse } from "./GetConversationSummaryResponse";
export type { GetHistoryEntryResponseEvent } from "./GetHistoryEntryResponseEvent";
export type { GetUserAgentResponse } from "./GetUserAgentResponse";
export type { GetUserSavedConfigResponse } from "./GetUserSavedConfigResponse";
export type { GhostCommit } from "./GhostCommit";
export type { GitDiffToRemoteParams } from "./GitDiffToRemoteParams";
export type { GitDiffToRemoteResponse } from "./GitDiffToRemoteResponse";
export type { GitSha } from "./GitSha";
export type { HistoryEntry } from "./HistoryEntry";
export type { ImageDetail } from "./ImageDetail";
export type { InitializeCapabilities } from "./InitializeCapabilities";
export type { InitializeParams } from "./InitializeParams";
export type { InitializeResponse } from "./InitializeResponse";
export type { InputItem } from "./InputItem";
export type { InputModality } from "./InputModality";
export type { InterruptConversationParams } from "./InterruptConversationParams";
export type { InterruptConversationResponse } from "./InterruptConversationResponse";
export type { ItemCompletedEvent } from "./ItemCompletedEvent";
export type { ItemStartedEvent } from "./ItemStartedEvent";
export type { ListConversationsParams } from "./ListConversationsParams";
export type { ListConversationsResponse } from "./ListConversationsResponse";
export type { ListCustomPromptsResponseEvent } from "./ListCustomPromptsResponseEvent";
export type { ListRemoteSkillsResponseEvent } from "./ListRemoteSkillsResponseEvent";
export type { ListSkillsResponseEvent } from "./ListSkillsResponseEvent";
export type { LocalShellAction } from "./LocalShellAction";
export type { LocalShellExecAction } from "./LocalShellExecAction";
export type { LocalShellStatus } from "./LocalShellStatus";
export type { LoginApiKeyParams } from "./LoginApiKeyParams";
export type { LoginApiKeyResponse } from "./LoginApiKeyResponse";
export type { LoginChatGptCompleteNotification } from "./LoginChatGptCompleteNotification";
export type { LoginChatGptResponse } from "./LoginChatGptResponse";
export type { LogoutChatGptResponse } from "./LogoutChatGptResponse";
export type { MacOsAutomationValue } from "./MacOsAutomationValue";
export type { MacOsPermissions } from "./MacOsPermissions";
export type { MacOsPreferencesValue } from "./MacOsPreferencesValue";
@@ -139,8 +117,6 @@ export type { NetworkApprovalContext } from "./NetworkApprovalContext";
export type { NetworkApprovalProtocol } from "./NetworkApprovalProtocol";
export type { NetworkPolicyAmendment } from "./NetworkPolicyAmendment";
export type { NetworkPolicyRuleAction } from "./NetworkPolicyRuleAction";
export type { NewConversationParams } from "./NewConversationParams";
export type { NewConversationResponse } from "./NewConversationResponse";
export type { ParsedCommand } from "./ParsedCommand";
export type { PatchApplyBeginEvent } from "./PatchApplyBeginEvent";
export type { PatchApplyEndEvent } from "./PatchApplyEndEvent";
@@ -151,7 +127,6 @@ export type { PlanDeltaEvent } from "./PlanDeltaEvent";
export type { PlanItem } from "./PlanItem";
export type { PlanItemArg } from "./PlanItemArg";
export type { PlanType } from "./PlanType";
export type { Profile } from "./Profile";
export type { RateLimitSnapshot } from "./RateLimitSnapshot";
export type { RateLimitWindow } from "./RateLimitWindow";
export type { RawResponseItemEvent } from "./RawResponseItemEvent";
@@ -161,6 +136,8 @@ export type { RealtimeConversationClosedEvent } from "./RealtimeConversationClos
export type { RealtimeConversationRealtimeEvent } from "./RealtimeConversationRealtimeEvent";
export type { RealtimeConversationStartedEvent } from "./RealtimeConversationStartedEvent";
export type { RealtimeEvent } from "./RealtimeEvent";
export type { RealtimeHandoffMessage } from "./RealtimeHandoffMessage";
export type { RealtimeHandoffRequested } from "./RealtimeHandoffRequested";
export type { ReasoningContentDeltaEvent } from "./ReasoningContentDeltaEvent";
export type { ReasoningEffort } from "./ReasoningEffort";
export type { ReasoningItem } from "./ReasoningItem";
@@ -171,8 +148,6 @@ export type { ReasoningSummary } from "./ReasoningSummary";
export type { RejectConfig } from "./RejectConfig";
export type { RemoteSkillDownloadedEvent } from "./RemoteSkillDownloadedEvent";
export type { RemoteSkillSummary } from "./RemoteSkillSummary";
export type { RemoveConversationListenerParams } from "./RemoveConversationListenerParams";
export type { RemoveConversationSubscriptionResponse } from "./RemoveConversationSubscriptionResponse";
export type { RequestId } from "./RequestId";
export type { RequestUserInputEvent } from "./RequestUserInputEvent";
export type { RequestUserInputQuestion } from "./RequestUserInputQuestion";
@@ -180,8 +155,6 @@ export type { RequestUserInputQuestionOption } from "./RequestUserInputQuestionO
export type { Resource } from "./Resource";
export type { ResourceTemplate } from "./ResourceTemplate";
export type { ResponseItem } from "./ResponseItem";
export type { ResumeConversationParams } from "./ResumeConversationParams";
export type { ResumeConversationResponse } from "./ResumeConversationResponse";
export type { ReviewCodeLocation } from "./ReviewCodeLocation";
export type { ReviewDecision } from "./ReviewDecision";
export type { ReviewFinding } from "./ReviewFinding";
@@ -189,21 +162,13 @@ export type { ReviewLineRange } from "./ReviewLineRange";
export type { ReviewOutputEvent } from "./ReviewOutputEvent";
export type { ReviewRequest } from "./ReviewRequest";
export type { ReviewTarget } from "./ReviewTarget";
export type { SandboxMode } from "./SandboxMode";
export type { SandboxPolicy } from "./SandboxPolicy";
export type { SandboxSettings } from "./SandboxSettings";
export type { SendUserMessageParams } from "./SendUserMessageParams";
export type { SendUserMessageResponse } from "./SendUserMessageResponse";
export type { SendUserTurnParams } from "./SendUserTurnParams";
export type { SendUserTurnResponse } from "./SendUserTurnResponse";
export type { ServerNotification } from "./ServerNotification";
export type { ServerRequest } from "./ServerRequest";
export type { ServiceTier } from "./ServiceTier";
export type { SessionConfiguredEvent } from "./SessionConfiguredEvent";
export type { SessionConfiguredNotification } from "./SessionConfiguredNotification";
export type { SessionNetworkProxyRuntime } from "./SessionNetworkProxyRuntime";
export type { SessionSource } from "./SessionSource";
export type { SetDefaultModelParams } from "./SetDefaultModelParams";
export type { SetDefaultModelResponse } from "./SetDefaultModelResponse";
export type { Settings } from "./Settings";
export type { SkillDependencies } from "./SkillDependencies";
export type { SkillErrorInfo } from "./SkillErrorInfo";
@@ -224,7 +189,6 @@ export type { TokenCountEvent } from "./TokenCountEvent";
export type { TokenUsage } from "./TokenUsage";
export type { TokenUsageInfo } from "./TokenUsageInfo";
export type { Tool } from "./Tool";
export type { Tools } from "./Tools";
export type { TurnAbortReason } from "./TurnAbortReason";
export type { TurnAbortedEvent } from "./TurnAbortedEvent";
export type { TurnCompleteEvent } from "./TurnCompleteEvent";
@@ -234,11 +198,9 @@ export type { TurnStartedEvent } from "./TurnStartedEvent";
export type { UndoCompletedEvent } from "./UndoCompletedEvent";
export type { UndoStartedEvent } from "./UndoStartedEvent";
export type { UpdatePlanArgs } from "./UpdatePlanArgs";
export type { UserInfoResponse } from "./UserInfoResponse";
export type { UserInput } from "./UserInput";
export type { UserMessageEvent } from "./UserMessageEvent";
export type { UserMessageItem } from "./UserMessageItem";
export type { UserSavedConfig } from "./UserSavedConfig";
export type { Verbosity } from "./Verbosity";
export type { ViewImageToolCallEvent } from "./ViewImageToolCallEvent";
export type { WarningEvent } from "./WarningEvent";

View File

@@ -4,6 +4,7 @@
import type { ForcedLoginMethod } from "../ForcedLoginMethod";
import type { ReasoningEffort } from "../ReasoningEffort";
import type { ReasoningSummary } from "../ReasoningSummary";
import type { ServiceTier } from "../ServiceTier";
import type { Verbosity } from "../Verbosity";
import type { WebSearchMode } from "../WebSearchMode";
import type { JsonValue } from "../serde_json/JsonValue";
@@ -14,4 +15,4 @@ import type { SandboxMode } from "./SandboxMode";
import type { SandboxWorkspaceWrite } from "./SandboxWorkspaceWrite";
import type { ToolsV2 } from "./ToolsV2";
export type Config = {model: string | null, review_model: string | null, model_context_window: bigint | null, model_auto_compact_token_limit: bigint | null, model_provider: string | null, approval_policy: AskForApproval | null, sandbox_mode: SandboxMode | null, sandbox_workspace_write: SandboxWorkspaceWrite | null, forced_chatgpt_workspace_id: string | null, forced_login_method: ForcedLoginMethod | null, web_search: WebSearchMode | null, tools: ToolsV2 | null, profile: string | null, profiles: { [key in string]?: ProfileV2 }, instructions: string | null, developer_instructions: string | null, compact_prompt: string | null, model_reasoning_effort: ReasoningEffort | null, model_reasoning_summary: ReasoningSummary | null, model_verbosity: Verbosity | null, analytics: AnalyticsConfig | null} & ({ [key in string]?: number | string | boolean | Array<JsonValue> | { [key in string]?: JsonValue } | null });
export type Config = {model: string | null, review_model: string | null, model_context_window: bigint | null, model_auto_compact_token_limit: bigint | null, model_provider: string | null, approval_policy: AskForApproval | null, sandbox_mode: SandboxMode | null, sandbox_workspace_write: SandboxWorkspaceWrite | null, forced_chatgpt_workspace_id: string | null, forced_login_method: ForcedLoginMethod | null, web_search: WebSearchMode | null, tools: ToolsV2 | null, profile: string | null, profiles: { [key in string]?: ProfileV2 }, instructions: string | null, developer_instructions: string | null, compact_prompt: string | null, model_reasoning_effort: ReasoningEffort | null, model_reasoning_summary: ReasoningSummary | null, model_verbosity: Verbosity | null, service_tier: ServiceTier | null, analytics: AnalyticsConfig | null} & ({ [key in string]?: number | string | boolean | Array<JsonValue> | { [key in string]?: JsonValue } | null });

View File

@@ -3,9 +3,10 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ReasoningEffort } from "../ReasoningEffort";
import type { ReasoningSummary } from "../ReasoningSummary";
import type { ServiceTier } from "../ServiceTier";
import type { Verbosity } from "../Verbosity";
import type { WebSearchMode } from "../WebSearchMode";
import type { JsonValue } from "../serde_json/JsonValue";
import type { AskForApproval } from "./AskForApproval";
export type ProfileV2 = { model: string | null, model_provider: string | null, approval_policy: AskForApproval | null, model_reasoning_effort: ReasoningEffort | null, model_reasoning_summary: ReasoningSummary | null, model_verbosity: Verbosity | null, web_search: WebSearchMode | null, chatgpt_base_url: string | null, } & ({ [key in string]?: number | string | boolean | Array<JsonValue> | { [key in string]?: JsonValue } | null });
export type ProfileV2 = { model: string | null, model_provider: string | null, approval_policy: AskForApproval | null, service_tier: ServiceTier | null, model_reasoning_effort: ReasoningEffort | null, model_reasoning_summary: ReasoningSummary | null, model_verbosity: Verbosity | null, web_search: WebSearchMode | null, chatgpt_base_url: string | null, } & ({ [key in string]?: number | string | boolean | Array<JsonValue> | { [key in string]?: JsonValue } | null });

View File

@@ -1,6 +1,7 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ServiceTier } from "../ServiceTier";
import type { JsonValue } from "../serde_json/JsonValue";
import type { AskForApproval } from "./AskForApproval";
import type { SandboxMode } from "./SandboxMode";
@@ -21,7 +22,7 @@ export type ThreadForkParams = {threadId: string, /**
path?: string | null, /**
* Configuration overrides for the forked thread, if any.
*/
model?: string | null, modelProvider?: string | null, cwd?: string | null, approvalPolicy?: AskForApproval | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, /**
model?: string | null, modelProvider?: string | null, serviceTier?: ServiceTier | null | null, cwd?: string | null, approvalPolicy?: AskForApproval | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, /**
* If true, persist additional rollout EventMsg variants required to
* reconstruct a richer thread history on subsequent resume/fork/read.
*/

View File

@@ -2,8 +2,9 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ReasoningEffort } from "../ReasoningEffort";
import type { ServiceTier } from "../ServiceTier";
import type { AskForApproval } from "./AskForApproval";
import type { SandboxPolicy } from "./SandboxPolicy";
import type { Thread } from "./Thread";
export type ThreadForkResponse = { thread: Thread, model: string, modelProvider: string, cwd: string, approvalPolicy: AskForApproval, sandbox: SandboxPolicy, reasoningEffort: ReasoningEffort | null, };
export type ThreadForkResponse = { thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: string, approvalPolicy: AskForApproval, sandbox: SandboxPolicy, reasoningEffort: ReasoningEffort | null, };

View File

@@ -0,0 +1,20 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type ThreadMetadataGitInfoUpdateParams = {
/**
* Omit to leave the stored commit unchanged, set to `null` to clear it,
* or provide a non-empty string to replace it.
*/
sha?: string | null,
/**
* Omit to leave the stored branch unchanged, set to `null` to clear it,
* or provide a non-empty string to replace it.
*/
branch?: string | null,
/**
* Omit to leave the stored origin URL unchanged, set to `null` to clear it,
* or provide a non-empty string to replace it.
*/
originUrl?: string | null, };

View File

@@ -0,0 +1,12 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ThreadMetadataGitInfoUpdateParams } from "./ThreadMetadataGitInfoUpdateParams";
export type ThreadMetadataUpdateParams = { threadId: string,
/**
* Patch the stored Git metadata for this thread.
* Omit a field to leave it unchanged, set it to `null` to clear it, or
* provide a string to replace the stored value.
*/
gitInfo?: ThreadMetadataGitInfoUpdateParams | null, };

View File

@@ -1,5 +1,6 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Thread } from "./Thread";
export type AddConversationSubscriptionResponse = { subscriptionId: string, };
export type ThreadMetadataUpdateResponse = { thread: Thread, };

View File

@@ -3,6 +3,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Personality } from "../Personality";
import type { ResponseItem } from "../ResponseItem";
import type { ServiceTier } from "../ServiceTier";
import type { JsonValue } from "../serde_json/JsonValue";
import type { AskForApproval } from "./AskForApproval";
import type { SandboxMode } from "./SandboxMode";
@@ -30,7 +31,7 @@ history?: Array<ResponseItem> | null, /**
path?: string | null, /**
* Configuration overrides for the resumed thread, if any.
*/
model?: string | null, modelProvider?: string | null, cwd?: string | null, approvalPolicy?: AskForApproval | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, personality?: Personality | null, /**
model?: string | null, modelProvider?: string | null, serviceTier?: ServiceTier | null | null, cwd?: string | null, approvalPolicy?: AskForApproval | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, personality?: Personality | null, /**
* If true, persist additional rollout EventMsg variants required to
* reconstruct a richer thread history on subsequent resume/fork/read.
*/

View File

@@ -2,8 +2,9 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ReasoningEffort } from "../ReasoningEffort";
import type { ServiceTier } from "../ServiceTier";
import type { AskForApproval } from "./AskForApproval";
import type { SandboxPolicy } from "./SandboxPolicy";
import type { Thread } from "./Thread";
export type ThreadResumeResponse = { thread: Thread, model: string, modelProvider: string, cwd: string, approvalPolicy: AskForApproval, sandbox: SandboxPolicy, reasoningEffort: ReasoningEffort | null, };
export type ThreadResumeResponse = { thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: string, approvalPolicy: AskForApproval, sandbox: SandboxPolicy, reasoningEffort: ReasoningEffort | null, };

View File

@@ -2,11 +2,12 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Personality } from "../Personality";
import type { ServiceTier } from "../ServiceTier";
import type { JsonValue } from "../serde_json/JsonValue";
import type { AskForApproval } from "./AskForApproval";
import type { SandboxMode } from "./SandboxMode";
export type ThreadStartParams = {model?: string | null, modelProvider?: string | null, cwd?: string | null, approvalPolicy?: AskForApproval | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, serviceName?: string | null, baseInstructions?: string | null, developerInstructions?: string | null, personality?: Personality | null, ephemeral?: boolean | null, /**
export type ThreadStartParams = {model?: string | null, modelProvider?: string | null, serviceTier?: ServiceTier | null | null, cwd?: string | null, approvalPolicy?: AskForApproval | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, serviceName?: string | null, baseInstructions?: string | null, developerInstructions?: string | null, personality?: Personality | null, ephemeral?: boolean | null, /**
* If true, opt into emitting raw Responses API items on the event stream.
* This is for internal use only (e.g. Codex Cloud).
*/

View File

@@ -2,8 +2,9 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ReasoningEffort } from "../ReasoningEffort";
import type { ServiceTier } from "../ServiceTier";
import type { AskForApproval } from "./AskForApproval";
import type { SandboxPolicy } from "./SandboxPolicy";
import type { Thread } from "./Thread";
export type ThreadStartResponse = { thread: Thread, model: string, modelProvider: string, cwd: string, approvalPolicy: AskForApproval, sandbox: SandboxPolicy, reasoningEffort: ReasoningEffort | null, };
export type ThreadStartResponse = { thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: string, approvalPolicy: AskForApproval, sandbox: SandboxPolicy, reasoningEffort: ReasoningEffort | null, };

View File

@@ -5,6 +5,7 @@ import type { CollaborationMode } from "../CollaborationMode";
import type { Personality } from "../Personality";
import type { ReasoningEffort } from "../ReasoningEffort";
import type { ReasoningSummary } from "../ReasoningSummary";
import type { ServiceTier } from "../ServiceTier";
import type { JsonValue } from "../serde_json/JsonValue";
import type { AskForApproval } from "./AskForApproval";
import type { SandboxPolicy } from "./SandboxPolicy";
@@ -23,6 +24,9 @@ sandboxPolicy?: SandboxPolicy | null, /**
* Override the model for this turn and subsequent turns.
*/
model?: string | null, /**
* Override the service tier for this turn and subsequent turns.
*/
serviceTier?: ServiceTier | null | null, /**
* Override the reasoning effort for this turn and subsequent turns.
*/
effort?: ReasoningEffort | null, /**

View File

@@ -179,6 +179,9 @@ export type { ThreadListParams } from "./ThreadListParams";
export type { ThreadListResponse } from "./ThreadListResponse";
export type { ThreadLoadedListParams } from "./ThreadLoadedListParams";
export type { ThreadLoadedListResponse } from "./ThreadLoadedListResponse";
export type { ThreadMetadataGitInfoUpdateParams } from "./ThreadMetadataGitInfoUpdateParams";
export type { ThreadMetadataUpdateParams } from "./ThreadMetadataUpdateParams";
export type { ThreadMetadataUpdateResponse } from "./ThreadMetadataUpdateResponse";
export type { ThreadNameUpdatedNotification } from "./ThreadNameUpdatedNotification";
export type { ThreadReadParams } from "./ThreadReadParams";
export type { ThreadReadResponse } from "./ThreadReadResponse";

View File

@@ -14,9 +14,21 @@ struct Args {
/// Optional Prettier executable path to format generated TypeScript files
#[arg(short = 'p', long = "prettier", value_name = "PRETTIER_BIN")]
prettier: Option<PathBuf>,
/// Include experimental API methods and fields in generated output.
#[arg(long = "experimental")]
experimental: bool,
}
fn main() -> Result<()> {
let args = Args::parse();
codex_app_server_protocol::generate_types(&args.out_dir, args.prettier.as_deref())
codex_app_server_protocol::generate_ts_with_options(
&args.out_dir,
args.prettier.as_deref(),
codex_app_server_protocol::GenerateTsOptions {
experimental_api: args.experimental,
..codex_app_server_protocol::GenerateTsOptions::default()
},
)?;
codex_app_server_protocol::generate_json_with_experimental(&args.out_dir, args.experimental)
}

View File

@@ -37,36 +37,17 @@ use ts_rs::TS;
const HEADER: &str = "// GENERATED CODE! DO NOT MODIFY BY HAND!\n\n";
const IGNORED_DEFINITIONS: &[&str] = &["Option<()>"];
const JSON_V1_ALLOWLIST: &[&str] = &["InitializeParams", "InitializeResponse"];
const V1_CLIENT_REQUEST_METHODS: &[&str] = &[
"newConversation",
"getConversationSummary",
"listConversations",
"resumeConversation",
"forkConversation",
"archiveConversation",
"sendUserMessage",
"sendUserTurn",
"interruptConversation",
"addConversationListener",
"removeConversationListener",
"gitDiffToRemote",
"loginApiKey",
"loginChatGpt",
"cancelLoginChatGpt",
"logoutChatGpt",
"getAuthStatus",
"getUserSavedConfig",
"setDefaultModel",
"getUserAgent",
"userInfo",
"execOneOffCommand",
];
const EXCLUDED_SERVER_NOTIFICATION_METHODS_FOR_JSON: &[&str] = &[
"authStatusChange",
"loginChatGptComplete",
"sessionConfigured",
"rawResponseItem/completed",
const SPECIAL_DEFINITIONS: &[&str] = &[
"ClientNotification",
"ClientRequest",
"EventMsg",
"ServerNotification",
"ServerRequest",
];
const FLAT_V2_SHARED_DEFINITIONS: &[&str] = &["ClientRequest", "EventMsg", "ServerNotification"];
const V1_CLIENT_REQUEST_METHODS: &[&str] =
&["getConversationSummary", "gitDiffToRemote", "getAuthStatus"];
const EXCLUDED_SERVER_NOTIFICATION_METHODS_FOR_JSON: &[&str] = &["rawResponseItem/completed"];
#[derive(Clone)]
pub struct GeneratedSchema {
@@ -136,6 +117,7 @@ pub fn generate_ts_with_options(
ServerRequest::export_all_to(out_dir)?;
export_server_responses(out_dir)?;
ServerNotification::export_all_to(out_dir)?;
EventMsg::export_all_to(out_dir)?;
if !options.experimental_api {
filter_experimental_ts(out_dir)?;
@@ -223,6 +205,11 @@ pub fn generate_json_with_experimental(out_dir: &Path, experimental_api: bool) -
out_dir.join("codex_app_server_protocol.schemas.json"),
&bundle,
)?;
let flat_v2_bundle = build_flat_v2_schema(&bundle)?;
write_pretty_json(
out_dir.join("codex_app_server_protocol.v2.schemas.json"),
&flat_v2_bundle,
)?;
if !experimental_api {
filter_experimental_json_files(out_dir)?;
@@ -870,14 +857,6 @@ impl Depth {
}
fn build_schema_bundle(schemas: Vec<GeneratedSchema>) -> Result<Value> {
const SPECIAL_DEFINITIONS: &[&str] = &[
"ClientNotification",
"ClientRequest",
"EventMsg",
"ServerNotification",
"ServerRequest",
];
let namespaced_types = collect_namespaced_types(&schemas);
let mut definitions = Map::new();
@@ -895,6 +874,8 @@ fn build_schema_bundle(schemas: Vec<GeneratedSchema>) -> Result<Value> {
if let Some(ref ns) = namespace {
rewrite_refs_to_namespace(&mut value, ns);
} else {
rewrite_refs_to_known_namespaces(&mut value, &namespaced_types);
}
let mut forced_namespace_refs: Vec<(String, String)> = Vec::new();
@@ -958,6 +939,210 @@ fn build_schema_bundle(schemas: Vec<GeneratedSchema>) -> Result<Value> {
Ok(Value::Object(root))
}
/// Build a datamodel-code-generator-friendly v2 bundle from the mixed export.
///
/// The full bundle keeps v2 schemas nested under `definitions.v2`, plus a few
/// shared root definitions like `ClientRequest`, `EventMsg`, and
/// `ServerNotification`. Python codegen only walks one definitions map level, so
/// a direct feed would treat `v2` itself as a schema and miss unreferenced v2
/// leaves. This helper flattens all v2 definitions to the root definitions map,
/// then pulls in the shared root schemas and any non-v2 transitive deps they
/// still reference. Keep the shared root unions intact here: some valid
/// request/notification/event variants are inline or only reference shared root
/// helpers, so filtering them by the presence of a `#/definitions/v2/` ref
/// would silently drop real API surface from the flat bundle.
fn build_flat_v2_schema(bundle: &Value) -> Result<Value> {
let Value::Object(root) = bundle else {
return Err(anyhow!("expected bundle root to be an object"));
};
let definitions = root
.get("definitions")
.and_then(Value::as_object)
.ok_or_else(|| anyhow!("expected bundle definitions map"))?;
let v2_definitions = definitions
.get("v2")
.and_then(Value::as_object)
.ok_or_else(|| anyhow!("expected v2 namespace in bundle definitions"))?;
let mut flat_root = root.clone();
let title = root
.get("title")
.and_then(Value::as_str)
.unwrap_or("CodexAppServerProtocol");
let mut flat_definitions = v2_definitions.clone();
let mut shared_definitions = Map::new();
let mut non_v2_refs = HashSet::new();
for shared in FLAT_V2_SHARED_DEFINITIONS {
let Some(shared_schema) = definitions.get(*shared) else {
continue;
};
let shared_schema = shared_schema.clone();
non_v2_refs.extend(collect_non_v2_refs(&shared_schema));
shared_definitions.insert((*shared).to_string(), shared_schema);
}
for name in collect_definition_dependencies(definitions, non_v2_refs) {
if name == "v2" || flat_definitions.contains_key(&name) {
continue;
}
if let Some(schema) = definitions.get(&name) {
flat_definitions.insert(name, schema.clone());
}
}
flat_definitions.extend(shared_definitions);
flat_root.insert("title".to_string(), Value::String(format!("{title}V2")));
flat_root.insert("definitions".to_string(), Value::Object(flat_definitions));
let mut flat_bundle = Value::Object(flat_root);
rewrite_ref_prefix(&mut flat_bundle, "#/definitions/v2/", "#/definitions/");
ensure_no_ref_prefix(&flat_bundle, "#/definitions/v2/", "flat v2")?;
ensure_referenced_definitions_present(&flat_bundle, "flat v2")?;
Ok(flat_bundle)
}
fn collect_non_v2_refs(value: &Value) -> HashSet<String> {
let mut refs = HashSet::new();
collect_non_v2_refs_inner(value, &mut refs);
refs
}
fn collect_non_v2_refs_inner(value: &Value, refs: &mut HashSet<String>) {
match value {
Value::Object(obj) => {
if let Some(Value::String(reference)) = obj.get("$ref")
&& let Some(name) = reference.strip_prefix("#/definitions/")
&& !reference.starts_with("#/definitions/v2/")
{
refs.insert(name.to_string());
}
for child in obj.values() {
collect_non_v2_refs_inner(child, refs);
}
}
Value::Array(items) => {
for child in items {
collect_non_v2_refs_inner(child, refs);
}
}
_ => {}
}
}
fn collect_definition_dependencies(
definitions: &Map<String, Value>,
names: HashSet<String>,
) -> HashSet<String> {
let mut seen = HashSet::new();
let mut to_process: Vec<String> = names.into_iter().collect();
while let Some(name) = to_process.pop() {
if !seen.insert(name.clone()) {
continue;
}
let Some(schema) = definitions.get(&name) else {
continue;
};
for dep in collect_non_v2_refs(schema) {
if !seen.contains(&dep) {
to_process.push(dep);
}
}
}
seen
}
fn rewrite_ref_prefix(value: &mut Value, prefix: &str, replacement: &str) {
match value {
Value::Object(obj) => {
if let Some(Value::String(reference)) = obj.get_mut("$ref") {
*reference = reference.replace(prefix, replacement);
}
for child in obj.values_mut() {
rewrite_ref_prefix(child, prefix, replacement);
}
}
Value::Array(items) => {
for child in items {
rewrite_ref_prefix(child, prefix, replacement);
}
}
_ => {}
}
}
fn ensure_no_ref_prefix(value: &Value, prefix: &str, label: &str) -> Result<()> {
if let Some(reference) = first_ref_with_prefix(value, prefix) {
return Err(anyhow!(
"{label} schema still references namespaced definitions; found {reference}"
));
}
Ok(())
}
fn first_ref_with_prefix(value: &Value, prefix: &str) -> Option<String> {
match value {
Value::Object(obj) => {
if let Some(Value::String(reference)) = obj.get("$ref")
&& reference.starts_with(prefix)
{
return Some(reference.clone());
}
obj.values()
.find_map(|child| first_ref_with_prefix(child, prefix))
}
Value::Array(items) => items
.iter()
.find_map(|child| first_ref_with_prefix(child, prefix)),
_ => None,
}
}
fn ensure_referenced_definitions_present(schema: &Value, label: &str) -> Result<()> {
let definitions = schema
.get("definitions")
.and_then(Value::as_object)
.ok_or_else(|| anyhow!("expected definitions map in {label} schema"))?;
let mut missing = HashSet::new();
collect_missing_definitions(schema, definitions, &mut missing);
if missing.is_empty() {
return Ok(());
}
let mut missing_names: Vec<String> = missing.into_iter().collect();
missing_names.sort();
Err(anyhow!(
"{label} schema missing definitions: {}",
missing_names.join(", ")
))
}
fn collect_missing_definitions(
value: &Value,
definitions: &Map<String, Value>,
missing: &mut HashSet<String>,
) {
match value {
Value::Object(obj) => {
if let Some(Value::String(reference)) = obj.get("$ref")
&& let Some(name) = reference.strip_prefix("#/definitions/")
{
let name = name.split('/').next().unwrap_or(name);
if !definitions.contains_key(name) {
missing.insert(name.to_string());
}
}
for child in obj.values() {
collect_missing_definitions(child, definitions, missing);
}
}
Value::Array(items) => {
for child in items {
collect_missing_definitions(child, definitions, missing);
}
}
_ => {}
}
}
fn insert_into_namespace(
definitions: &mut Map<String, Value>,
namespace: &str,
@@ -1230,6 +1415,43 @@ fn rewrite_refs_to_namespace(value: &mut Value, ns: &str) {
}
}
/// Recursively rewrite bare root definition refs to the namespace that owns the
/// referenced type in the bundle.
///
/// The mixed export contains shared root helper schemas that are intentionally
/// left outside the `v2` namespace, but some of their extracted child
/// definitions still contain refs like `#/definitions/ThreadId`. When the real
/// schema only exists under `#/definitions/v2/ThreadId`, those refs become
/// dangling and downstream codegen falls back to placeholder `Any` models. This
/// rewrite keeps the shared helpers at the root while retargeting their refs to
/// the namespaced definitions that actually exist.
fn rewrite_refs_to_known_namespaces(value: &mut Value, types: &HashMap<String, String>) {
match value {
Value::Object(obj) => {
if let Some(Value::String(reference)) = obj.get_mut("$ref")
&& let Some(suffix) = reference.strip_prefix("#/definitions/")
{
let (name, tail) = suffix
.split_once('/')
.map_or((suffix, None), |(name, tail)| (name, Some(tail)));
if let Some(ns) = namespace_for_definition(name, types) {
let tail = tail.map_or(String::new(), |rest| format!("/{rest}"));
*reference = format!("#/definitions/{ns}/{name}{tail}");
}
}
for v in obj.values_mut() {
rewrite_refs_to_known_namespaces(v, types);
}
}
Value::Array(items) => {
for v in items.iter_mut() {
rewrite_refs_to_known_namespaces(v, types);
}
}
_ => {}
}
}
fn collect_namespaced_types(schemas: &[GeneratedSchema]) -> HashMap<String, String> {
let mut types = HashMap::new();
for schema in schemas {
@@ -1689,6 +1911,7 @@ mod tests {
client_request_ts.contains("MockExperimentalMethodParams"),
false
);
assert_eq!(output_dir.join("EventMsg.ts").exists(), true);
let thread_start_ts =
fs::read_to_string(output_dir.join("v2").join("ThreadStartParams.ts"))?;
assert_eq!(thread_start_ts.contains("mockExperimentalField"), false);
@@ -1986,6 +2209,284 @@ mod tests {
Ok(())
}
#[test]
fn build_schema_bundle_rewrites_root_helper_refs_to_namespaced_defs() -> Result<()> {
let bundle = build_schema_bundle(vec![
GeneratedSchema {
namespace: None,
logical_name: "LegacyEnvelope".to_string(),
in_v1_dir: false,
value: serde_json::json!({
"title": "LegacyEnvelope",
"type": "object",
"properties": {
"current_thread": { "$ref": "#/definitions/ThreadId" },
"turn_item": { "$ref": "#/definitions/TurnItem" }
},
"definitions": {
"TurnItem": {
"type": "object",
"properties": {
"thread_id": { "$ref": "#/definitions/ThreadId" },
"phase": { "$ref": "#/definitions/MessagePhase" },
"content": {
"type": "array",
"items": { "$ref": "#/definitions/UserInput" }
}
}
}
}
}),
},
GeneratedSchema {
namespace: Some("v2".to_string()),
logical_name: "ThreadId".to_string(),
in_v1_dir: false,
value: serde_json::json!({
"title": "ThreadId",
"type": "string"
}),
},
GeneratedSchema {
namespace: Some("v2".to_string()),
logical_name: "MessagePhase".to_string(),
in_v1_dir: false,
value: serde_json::json!({
"title": "MessagePhase",
"type": "string"
}),
},
GeneratedSchema {
namespace: Some("v2".to_string()),
logical_name: "UserInput".to_string(),
in_v1_dir: false,
value: serde_json::json!({
"title": "UserInput",
"type": "string"
}),
},
])?;
assert_eq!(
bundle["definitions"]["LegacyEnvelope"]["properties"]["current_thread"]["$ref"],
serde_json::json!("#/definitions/v2/ThreadId")
);
assert_eq!(
bundle["definitions"]["LegacyEnvelope"]["properties"]["turn_item"]["$ref"],
serde_json::json!("#/definitions/TurnItem")
);
assert_eq!(
bundle["definitions"]["TurnItem"]["properties"]["thread_id"]["$ref"],
serde_json::json!("#/definitions/v2/ThreadId")
);
assert_eq!(
bundle["definitions"]["TurnItem"]["properties"]["phase"]["$ref"],
serde_json::json!("#/definitions/v2/MessagePhase")
);
assert_eq!(
bundle["definitions"]["TurnItem"]["properties"]["content"]["items"]["$ref"],
serde_json::json!("#/definitions/v2/UserInput")
);
Ok(())
}
#[test]
fn build_flat_v2_schema_keeps_shared_root_schemas_and_dependencies() -> Result<()> {
let bundle = serde_json::json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "CodexAppServerProtocol",
"type": "object",
"definitions": {
"ClientRequest": {
"oneOf": [
{
"title": "StartRequest",
"type": "object",
"properties": {
"params": { "$ref": "#/definitions/v2/ThreadStartParams" },
"shared": { "$ref": "#/definitions/SharedHelper" }
}
},
{
"title": "InitializeRequest",
"type": "object",
"properties": {
"params": { "$ref": "#/definitions/InitializeParams" }
}
},
{
"title": "LogoutRequest",
"type": "object",
"properties": {
"params": { "type": "null" }
}
}
]
},
"EventMsg": {
"oneOf": [
{ "$ref": "#/definitions/v2/ThreadStartedEventMsg" },
{
"title": "WarningEventMsg",
"type": "object",
"properties": {
"message": { "type": "string" },
"type": {
"enum": ["warning"],
"type": "string"
}
},
"required": ["message", "type"]
}
]
},
"ServerNotification": {
"oneOf": [
{ "$ref": "#/definitions/v2/ThreadStartedNotification" },
{
"title": "ServerRequestResolvedNotification",
"type": "object",
"properties": {
"params": { "$ref": "#/definitions/ServerRequestResolvedNotificationPayload" }
}
}
]
},
"SharedHelper": {
"type": "object",
"properties": {
"leaf": { "$ref": "#/definitions/SharedLeaf" }
}
},
"SharedLeaf": {
"title": "SharedLeaf",
"type": "string"
},
"InitializeParams": {
"title": "InitializeParams",
"type": "string"
},
"ServerRequestResolvedNotificationPayload": {
"title": "ServerRequestResolvedNotificationPayload",
"type": "string"
},
"v2": {
"ThreadStartParams": {
"title": "ThreadStartParams",
"type": "object",
"properties": {
"cwd": { "type": "string" }
}
},
"ThreadStartResponse": {
"title": "ThreadStartResponse",
"type": "object",
"properties": {
"ok": { "type": "boolean" }
}
},
"ThreadStartedEventMsg": {
"title": "ThreadStartedEventMsg",
"type": "object",
"properties": {
"thread_id": { "type": "string" }
}
},
"ThreadStartedNotification": {
"title": "ThreadStartedNotification",
"type": "object",
"properties": {
"thread_id": { "type": "string" }
}
}
}
}
});
let flat_bundle = build_flat_v2_schema(&bundle)?;
let definitions = flat_bundle["definitions"]
.as_object()
.expect("flat v2 schema should include definitions");
assert_eq!(
flat_bundle["title"],
serde_json::json!("CodexAppServerProtocolV2")
);
assert_eq!(definitions.contains_key("v2"), false);
assert_eq!(definitions.contains_key("ThreadStartParams"), true);
assert_eq!(definitions.contains_key("ThreadStartResponse"), true);
assert_eq!(definitions.contains_key("ThreadStartedEventMsg"), true);
assert_eq!(definitions.contains_key("ThreadStartedNotification"), true);
assert_eq!(definitions.contains_key("SharedHelper"), true);
assert_eq!(definitions.contains_key("SharedLeaf"), true);
assert_eq!(definitions.contains_key("InitializeParams"), true);
assert_eq!(
definitions.contains_key("ServerRequestResolvedNotificationPayload"),
true
);
let client_request_titles: BTreeSet<String> = definitions["ClientRequest"]["oneOf"]
.as_array()
.expect("ClientRequest should remain a oneOf")
.iter()
.map(|variant| {
variant["title"]
.as_str()
.expect("ClientRequest variant should have a title")
.to_string()
})
.collect();
assert_eq!(
client_request_titles,
BTreeSet::from([
"InitializeRequest".to_string(),
"LogoutRequest".to_string(),
"StartRequest".to_string(),
])
);
let event_titles: BTreeSet<String> = definitions["EventMsg"]["oneOf"]
.as_array()
.expect("EventMsg should remain a oneOf")
.iter()
.map(|variant| {
variant
.get("title")
.and_then(Value::as_str)
.unwrap_or_default()
.to_string()
})
.collect();
assert_eq!(
event_titles,
BTreeSet::from(["".to_string(), "WarningEventMsg".to_string(),])
);
let notification_titles: BTreeSet<String> = definitions["ServerNotification"]["oneOf"]
.as_array()
.expect("ServerNotification should remain a oneOf")
.iter()
.map(|variant| {
variant
.get("title")
.and_then(Value::as_str)
.unwrap_or_default()
.to_string()
})
.collect();
assert_eq!(
notification_titles,
BTreeSet::from([
"".to_string(),
"ServerRequestResolvedNotification".to_string(),
])
);
assert_eq!(
first_ref_with_prefix(&flat_bundle, "#/definitions/v2/").is_none(),
true
);
Ok(())
}
#[test]
fn experimental_type_fields_ts_filter_handles_interface_shape() -> Result<()> {
let output_dir = std::env::temp_dir().join(format!("codex_ts_filter_{}", Uuid::now_v7()));
@@ -2114,6 +2615,99 @@ export type Config = { stableField: Keep, unstableField: string | null } & ({ [k
bundle_json.contains("MockExperimentalMethodResponse"),
false
);
let flat_v2_bundle_json =
fs::read_to_string(output_dir.join("codex_app_server_protocol.v2.schemas.json"))?;
assert_eq!(flat_v2_bundle_json.contains("mockExperimentalField"), false);
assert_eq!(flat_v2_bundle_json.contains("additionalPermissions"), false);
assert_eq!(
flat_v2_bundle_json.contains("MockExperimentalMethodParams"),
false
);
assert_eq!(
flat_v2_bundle_json.contains("MockExperimentalMethodResponse"),
false
);
assert_eq!(flat_v2_bundle_json.contains("#/definitions/v2/"), false);
assert_eq!(
flat_v2_bundle_json.contains("\"title\": \"CodexAppServerProtocolV2\""),
true
);
let flat_v2_bundle =
read_json_value(&output_dir.join("codex_app_server_protocol.v2.schemas.json"))?;
let definitions = flat_v2_bundle["definitions"]
.as_object()
.expect("flat v2 bundle should include definitions");
let client_request_methods: BTreeSet<String> = definitions["ClientRequest"]["oneOf"]
.as_array()
.expect("flat v2 ClientRequest should remain a oneOf")
.iter()
.filter_map(|variant| {
variant["properties"]["method"]["enum"]
.as_array()
.and_then(|values| values.first())
.and_then(Value::as_str)
.map(str::to_string)
})
.collect();
let missing_client_request_methods: Vec<String> = [
"account/logout",
"account/rateLimits/read",
"config/mcpServer/reload",
"configRequirements/read",
"fuzzyFileSearch",
"initialize",
]
.into_iter()
.filter(|method| !client_request_methods.contains(*method))
.map(str::to_string)
.collect();
assert_eq!(missing_client_request_methods, Vec::<String>::new());
let server_notification_methods: BTreeSet<String> =
definitions["ServerNotification"]["oneOf"]
.as_array()
.expect("flat v2 ServerNotification should remain a oneOf")
.iter()
.filter_map(|variant| {
variant["properties"]["method"]["enum"]
.as_array()
.and_then(|values| values.first())
.and_then(Value::as_str)
.map(str::to_string)
})
.collect();
let missing_server_notification_methods: Vec<String> = [
"fuzzyFileSearch/sessionCompleted",
"fuzzyFileSearch/sessionUpdated",
"serverRequest/resolved",
]
.into_iter()
.filter(|method| !server_notification_methods.contains(*method))
.map(str::to_string)
.collect();
assert_eq!(missing_server_notification_methods, Vec::<String>::new());
let event_types: BTreeSet<String> = definitions["EventMsg"]["oneOf"]
.as_array()
.expect("flat v2 EventMsg should remain a oneOf")
.iter()
.filter_map(|variant| {
variant["properties"]["type"]["enum"]
.as_array()
.and_then(|values| values.first())
.and_then(Value::as_str)
.map(str::to_string)
})
.collect();
let missing_event_types: Vec<String> = [
"agent_message_delta",
"task_complete",
"warning",
"web_search_begin",
]
.into_iter()
.filter(|event_type| !event_types.contains(*event_type))
.map(str::to_string)
.collect();
assert_eq!(missing_event_types, Vec::<String>::new());
assert_eq!(
output_dir
.join("v2")

View File

@@ -1,6 +1,7 @@
//! We do not do true JSON-RPC 2.0, as we neither send nor expect the
//! "jsonrpc": "2.0" field.
use codex_protocol::protocol::W3cTraceContext;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
@@ -38,6 +39,10 @@ pub struct JSONRPCRequest {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub params: Option<serde_json::Value>,
/// Optional W3C Trace Context for distributed tracing.
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub trace: Option<W3cTraceContext>,
}
/// A notification which does not expect a response.

View File

@@ -211,6 +211,10 @@ client_request_definitions! {
params: v2::ThreadSetNameParams,
response: v2::ThreadSetNameResponse,
},
ThreadMetadataUpdate => "thread/metadata/update" {
params: v2::ThreadMetadataUpdateParams,
response: v2::ThreadMetadataUpdateResponse,
},
ThreadUnarchive => "thread/unarchive" {
params: v2::ThreadUnarchiveParams,
response: v2::ThreadUnarchiveResponse,
@@ -403,95 +407,19 @@ client_request_definitions! {
},
/// DEPRECATED APIs below
NewConversation {
params: v1::NewConversationParams,
response: v1::NewConversationResponse,
},
GetConversationSummary {
params: v1::GetConversationSummaryParams,
response: v1::GetConversationSummaryResponse,
},
/// List recorded Codex conversations (rollouts) with optional pagination and search.
ListConversations {
params: v1::ListConversationsParams,
response: v1::ListConversationsResponse,
},
/// Resume a recorded Codex conversation from a rollout file.
ResumeConversation {
params: v1::ResumeConversationParams,
response: v1::ResumeConversationResponse,
},
/// Fork a recorded Codex conversation into a new session.
ForkConversation {
params: v1::ForkConversationParams,
response: v1::ForkConversationResponse,
},
ArchiveConversation {
params: v1::ArchiveConversationParams,
response: v1::ArchiveConversationResponse,
},
SendUserMessage {
params: v1::SendUserMessageParams,
response: v1::SendUserMessageResponse,
},
SendUserTurn {
params: v1::SendUserTurnParams,
response: v1::SendUserTurnResponse,
},
InterruptConversation {
params: v1::InterruptConversationParams,
response: v1::InterruptConversationResponse,
},
AddConversationListener {
params: v1::AddConversationListenerParams,
response: v1::AddConversationSubscriptionResponse,
},
RemoveConversationListener {
params: v1::RemoveConversationListenerParams,
response: v1::RemoveConversationSubscriptionResponse,
},
GitDiffToRemote {
params: v1::GitDiffToRemoteParams,
response: v1::GitDiffToRemoteResponse,
},
LoginApiKey {
params: v1::LoginApiKeyParams,
response: v1::LoginApiKeyResponse,
},
LoginChatGpt {
params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>,
response: v1::LoginChatGptResponse,
},
// DEPRECATED in favor of CancelLoginAccount
CancelLoginChatGpt {
params: v1::CancelLoginChatGptParams,
response: v1::CancelLoginChatGptResponse,
},
LogoutChatGpt {
params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>,
response: v1::LogoutChatGptResponse,
},
/// DEPRECATED in favor of GetAccount
GetAuthStatus {
params: v1::GetAuthStatusParams,
response: v1::GetAuthStatusResponse,
},
GetUserSavedConfig {
params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>,
response: v1::GetUserSavedConfigResponse,
},
SetDefaultModel {
params: v1::SetDefaultModelParams,
response: v1::SetDefaultModelResponse,
},
GetUserAgent {
params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>,
response: v1::GetUserAgentResponse,
},
UserInfo {
params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>,
response: v1::UserInfoResponse,
},
FuzzyFileSearch {
params: FuzzyFileSearchParams,
response: FuzzyFileSearchResponse,
@@ -511,11 +439,6 @@ client_request_definitions! {
params: FuzzyFileSearchSessionStopParams,
response: FuzzyFileSearchSessionStopResponse,
},
/// Execute a command (argv vector) under the server's sandbox.
ExecOneOffCommand {
params: v1::ExecOneOffCommandParams,
response: v1::ExecOneOffCommandResponse,
},
}
/// Generates an `enum ServerRequest` where each variant is a request that the
@@ -882,12 +805,6 @@ server_notification_definitions! {
#[strum(serialize = "account/login/completed")]
AccountLoginCompleted(v2::AccountLoginCompletedNotification),
/// DEPRECATED NOTIFICATIONS below
AuthStatusChange(v1::AuthStatusChangeNotification),
/// Deprecated: use `account/login/completed` instead.
LoginChatGptComplete(v1::LoginChatGptCompleteNotification),
SessionConfigured(v1::SessionConfiguredNotification),
}
client_notification_definitions! {
@@ -901,7 +818,6 @@ mod tests {
use codex_protocol::ThreadId;
use codex_protocol::account::PlanType;
use codex_protocol::parse_command::ParsedCommand;
use codex_protocol::protocol::AskForApproval;
use codex_utils_absolute_path::AbsolutePathBuf;
use pretty_assertions::assert_eq;
use serde_json::json;
@@ -912,37 +828,19 @@ mod tests {
}
#[test]
fn serialize_new_conversation() -> Result<()> {
let request = ClientRequest::NewConversation {
fn serialize_get_conversation_summary() -> Result<()> {
let request = ClientRequest::GetConversationSummary {
request_id: RequestId::Integer(42),
params: v1::NewConversationParams {
model: Some("gpt-5.1-codex-max".to_string()),
model_provider: None,
profile: None,
cwd: None,
approval_policy: Some(AskForApproval::OnRequest),
sandbox: None,
config: None,
base_instructions: None,
developer_instructions: None,
compact_prompt: None,
include_apply_patch_tool: None,
params: v1::GetConversationSummaryParams::ThreadId {
conversation_id: ThreadId::from_string("67e55044-10b1-426f-9247-bb680e5fe0c8")?,
},
};
assert_eq!(
json!({
"method": "newConversation",
"method": "getConversationSummary",
"id": 42,
"params": {
"model": "gpt-5.1-codex-max",
"modelProvider": null,
"profile": null,
"cwd": null,
"approvalPolicy": "on-request",
"sandbox": null,
"config": null,
"baseInstructions": null,
"includeApplyPatchTool": null
"conversationId": "67e55044-10b1-426f-9247-bb680e5fe0c8"
}
}),
serde_json::to_value(&request)?,

View File

@@ -3,6 +3,7 @@
pub mod common;
mod mappers;
mod serde_helpers;
pub mod thread_history;
pub mod v1;
pub mod v2;

View File

@@ -0,0 +1,23 @@
use serde::Deserialize;
use serde::Deserializer;
use serde::Serialize;
use serde::Serializer;
pub fn deserialize_double_option<'de, T, D>(deserializer: D) -> Result<Option<Option<T>>, D::Error>
where
T: Deserialize<'de>,
D: Deserializer<'de>,
{
serde_with::rust::double_option::deserialize(deserializer)
}
pub fn serialize_double_option<T, S>(
value: &Option<Option<T>>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
T: Serialize,
S: Serializer,
{
serde_with::rust::double_option::serialize(value, serializer)
}

View File

@@ -5,6 +5,7 @@ use codex_protocol::ThreadId;
use codex_protocol::config_types::ForcedLoginMethod;
use codex_protocol::config_types::ReasoningSummary;
use codex_protocol::config_types::SandboxMode;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::config_types::Verbosity;
use codex_protocol::models::ResponseItem;
use codex_protocol::openai_models::ReasoningEffort;
@@ -419,6 +420,13 @@ pub struct SendUserTurnParams {
pub approval_policy: AskForApproval,
pub sandbox_policy: SandboxPolicy,
pub model: String,
#[serde(
default,
deserialize_with = "super::serde_helpers::deserialize_double_option",
serialize_with = "super::serde_helpers::serialize_double_option",
skip_serializing_if = "Option::is_none"
)]
pub service_tier: Option<Option<ServiceTier>>,
pub effort: Option<ReasoningEffort>,
pub summary: ReasoningSummary,
/// Optional JSON Schema used to constrain the final assistant message for this turn.
@@ -429,6 +437,55 @@ pub struct SendUserTurnParams {
#[serde(rename_all = "camelCase")]
pub struct SendUserTurnResponse {}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
use std::path::PathBuf;
#[test]
fn send_user_turn_params_preserve_explicit_null_service_tier() {
let params = SendUserTurnParams {
conversation_id: ThreadId::new(),
items: vec![],
cwd: PathBuf::from("/tmp"),
approval_policy: AskForApproval::Never,
sandbox_policy: SandboxPolicy::DangerFullAccess,
model: "gpt-4.1".to_string(),
service_tier: Some(None),
effort: None,
summary: ReasoningSummary::Auto,
output_schema: None,
};
let serialized = serde_json::to_value(&params).expect("params should serialize");
assert_eq!(
serialized.get("serviceTier"),
Some(&serde_json::Value::Null)
);
let roundtrip: SendUserTurnParams =
serde_json::from_value(serialized).expect("params should deserialize");
assert_eq!(roundtrip.service_tier, Some(None));
let without_override = SendUserTurnParams {
conversation_id: ThreadId::new(),
items: vec![],
cwd: PathBuf::from("/tmp"),
approval_policy: AskForApproval::Never,
sandbox_policy: SandboxPolicy::DangerFullAccess,
model: "gpt-4.1".to_string(),
service_tier: None,
effort: None,
summary: ReasoningSummary::Auto,
output_schema: None,
};
let serialized_without_override =
serde_json::to_value(&without_override).expect("params should serialize");
assert_eq!(serialized_without_override.get("serviceTier"), None);
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct InterruptConversationParams {
@@ -539,33 +596,3 @@ impl InputItem {
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
/// Deprecated in favor of AccountLoginCompletedNotification.
pub struct LoginChatGptCompleteNotification {
#[schemars(with = "String")]
pub login_id: Uuid,
pub success: bool,
pub error: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub struct SessionConfiguredNotification {
pub session_id: ThreadId,
pub model: String,
pub reasoning_effort: Option<ReasoningEffort>,
pub history_log_id: u64,
#[ts(type = "number")]
pub history_entry_count: usize,
pub initial_messages: Option<Vec<EventMsg>>,
pub rollout_path: PathBuf,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
/// Deprecated notification. Use AccountUpdatedNotification instead.
pub struct AuthStatusChangeNotification {
pub auth_method: Option<AuthMode>,
}

View File

@@ -17,6 +17,7 @@ use codex_protocol::config_types::ModeKind;
use codex_protocol::config_types::Personality;
use codex_protocol::config_types::ReasoningSummary;
use codex_protocol::config_types::SandboxMode as CoreSandboxMode;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::config_types::Verbosity;
use codex_protocol::config_types::WebSearchMode;
use codex_protocol::items::AgentMessageContent as CoreAgentMessageContent;
@@ -392,6 +393,7 @@ pub struct ProfileV2 {
pub model: Option<String>,
pub model_provider: Option<String>,
pub approval_policy: Option<AskForApproval>,
pub service_tier: Option<ServiceTier>,
pub model_reasoning_effort: Option<ReasoningEffort>,
pub model_reasoning_summary: Option<ReasoningSummary>,
pub model_verbosity: Option<Verbosity>,
@@ -503,6 +505,7 @@ pub struct Config {
pub model_reasoning_effort: Option<ReasoningEffort>,
pub model_reasoning_summary: Option<ReasoningSummary>,
pub model_verbosity: Option<Verbosity>,
pub service_tier: Option<ServiceTier>,
pub analytics: Option<AnalyticsConfig>,
#[experimental("config/read.apps")]
#[serde(default)]
@@ -1788,6 +1791,14 @@ pub struct ThreadStartParams {
pub model: Option<String>,
#[ts(optional = nullable)]
pub model_provider: Option<String>,
#[serde(
default,
deserialize_with = "super::serde_helpers::deserialize_double_option",
serialize_with = "super::serde_helpers::serialize_double_option",
skip_serializing_if = "Option::is_none"
)]
#[ts(optional = nullable)]
pub service_tier: Option<Option<ServiceTier>>,
#[ts(optional = nullable)]
pub cwd: Option<String>,
#[ts(optional = nullable)]
@@ -1850,6 +1861,7 @@ pub struct ThreadStartResponse {
pub thread: Thread,
pub model: String,
pub model_provider: String,
pub service_tier: Option<ServiceTier>,
pub cwd: PathBuf,
pub approval_policy: AskForApproval,
pub sandbox: SandboxPolicy,
@@ -1891,6 +1903,14 @@ pub struct ThreadResumeParams {
pub model: Option<String>,
#[ts(optional = nullable)]
pub model_provider: Option<String>,
#[serde(
default,
deserialize_with = "super::serde_helpers::deserialize_double_option",
serialize_with = "super::serde_helpers::serialize_double_option",
skip_serializing_if = "Option::is_none"
)]
#[ts(optional = nullable)]
pub service_tier: Option<Option<ServiceTier>>,
#[ts(optional = nullable)]
pub cwd: Option<String>,
#[ts(optional = nullable)]
@@ -1919,6 +1939,7 @@ pub struct ThreadResumeResponse {
pub thread: Thread,
pub model: String,
pub model_provider: String,
pub service_tier: Option<ServiceTier>,
pub cwd: PathBuf,
pub approval_policy: AskForApproval,
pub sandbox: SandboxPolicy,
@@ -1951,6 +1972,14 @@ pub struct ThreadForkParams {
pub model: Option<String>,
#[ts(optional = nullable)]
pub model_provider: Option<String>,
#[serde(
default,
deserialize_with = "super::serde_helpers::deserialize_double_option",
serialize_with = "super::serde_helpers::serialize_double_option",
skip_serializing_if = "Option::is_none"
)]
#[ts(optional = nullable)]
pub service_tier: Option<Option<ServiceTier>>,
#[ts(optional = nullable)]
pub cwd: Option<String>,
#[ts(optional = nullable)]
@@ -1977,6 +2006,7 @@ pub struct ThreadForkResponse {
pub thread: Thread,
pub model: String,
pub model_provider: String,
pub service_tier: Option<ServiceTier>,
pub cwd: PathBuf,
pub approval_policy: AskForApproval,
pub sandbox: SandboxPolicy,
@@ -2038,6 +2068,61 @@ pub struct ThreadUnarchiveParams {
#[ts(export_to = "v2/")]
pub struct ThreadSetNameResponse {}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadMetadataUpdateParams {
pub thread_id: String,
/// Patch the stored Git metadata for this thread.
/// Omit a field to leave it unchanged, set it to `null` to clear it, or
/// provide a string to replace the stored value.
#[ts(optional = nullable)]
pub git_info: Option<ThreadMetadataGitInfoUpdateParams>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadMetadataGitInfoUpdateParams {
/// Omit to leave the stored commit unchanged, set to `null` to clear it,
/// or provide a non-empty string to replace it.
#[serde(
default,
skip_serializing_if = "Option::is_none",
serialize_with = "super::serde_helpers::serialize_double_option",
deserialize_with = "super::serde_helpers::deserialize_double_option"
)]
#[ts(optional = nullable, type = "string | null")]
pub sha: Option<Option<String>>,
/// Omit to leave the stored branch unchanged, set to `null` to clear it,
/// or provide a non-empty string to replace it.
#[serde(
default,
skip_serializing_if = "Option::is_none",
serialize_with = "super::serde_helpers::serialize_double_option",
deserialize_with = "super::serde_helpers::deserialize_double_option"
)]
#[ts(optional = nullable, type = "string | null")]
pub branch: Option<Option<String>>,
/// Omit to leave the stored origin URL unchanged, set to `null` to clear it,
/// or provide a non-empty string to replace it.
#[serde(
default,
skip_serializing_if = "Option::is_none",
serialize_with = "super::serde_helpers::serialize_double_option",
deserialize_with = "super::serde_helpers::deserialize_double_option"
)]
#[ts(optional = nullable, type = "string | null")]
pub origin_url: Option<Option<String>>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadMetadataUpdateResponse {
pub thread: Thread,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
@@ -2837,6 +2922,15 @@ pub struct TurnStartParams {
/// Override the model for this turn and subsequent turns.
#[ts(optional = nullable)]
pub model: Option<String>,
/// Override the service tier for this turn and subsequent turns.
#[serde(
default,
deserialize_with = "super::serde_helpers::deserialize_double_option",
serialize_with = "super::serde_helpers::serialize_double_option",
skip_serializing_if = "Option::is_none"
)]
#[ts(optional = nullable)]
pub service_tier: Option<Option<ServiceTier>>,
/// Override the reasoning effort for this turn and subsequent turns.
#[ts(optional = nullable)]
pub effort: Option<ReasoningEffort>,
@@ -4566,4 +4660,56 @@ mod tests {
})
);
}
#[test]
fn thread_start_params_preserve_explicit_null_service_tier() {
let params: ThreadStartParams = serde_json::from_value(json!({ "serviceTier": null }))
.expect("params should deserialize");
assert_eq!(params.service_tier, Some(None));
let serialized = serde_json::to_value(&params).expect("params should serialize");
assert_eq!(
serialized.get("serviceTier"),
Some(&serde_json::Value::Null)
);
let serialized_without_override =
serde_json::to_value(ThreadStartParams::default()).expect("params should serialize");
assert_eq!(serialized_without_override.get("serviceTier"), None);
}
#[test]
fn turn_start_params_preserve_explicit_null_service_tier() {
let params: TurnStartParams = serde_json::from_value(json!({
"threadId": "thread_123",
"input": [],
"serviceTier": null
}))
.expect("params should deserialize");
assert_eq!(params.service_tier, Some(None));
let serialized = serde_json::to_value(&params).expect("params should serialize");
assert_eq!(
serialized.get("serviceTier"),
Some(&serde_json::Value::Null)
);
let without_override = TurnStartParams {
thread_id: "thread_123".to_string(),
input: vec![],
cwd: None,
approval_policy: None,
sandbox_policy: None,
model: None,
service_tier: None,
effort: None,
summary: None,
output_schema: None,
collaboration_mode: None,
personality: None,
};
let serialized_without_override =
serde_json::to_value(&without_override).expect("params should serialize");
assert_eq!(serialized_without_override.get("serviceTier"), None);
}
}

View File

@@ -15,6 +15,7 @@ use std::process::Command;
use std::process::Stdio;
use std::thread;
use std::time::Duration;
use std::time::SystemTime;
use anyhow::Context;
use anyhow::Result;
@@ -22,8 +23,7 @@ use anyhow::bail;
use clap::ArgAction;
use clap::Parser;
use clap::Subcommand;
use codex_app_server_protocol::AddConversationListenerParams;
use codex_app_server_protocol::AddConversationSubscriptionResponse;
use codex_app_server_protocol::AccountLoginCompletedNotification;
use codex_app_server_protocol::AskForApproval;
use codex_app_server_protocol::ClientInfo;
use codex_app_server_protocol::ClientRequest;
@@ -39,22 +39,16 @@ use codex_app_server_protocol::GetAccountRateLimitsResponse;
use codex_app_server_protocol::InitializeCapabilities;
use codex_app_server_protocol::InitializeParams;
use codex_app_server_protocol::InitializeResponse;
use codex_app_server_protocol::InputItem;
use codex_app_server_protocol::JSONRPCMessage;
use codex_app_server_protocol::JSONRPCNotification;
use codex_app_server_protocol::JSONRPCRequest;
use codex_app_server_protocol::JSONRPCResponse;
use codex_app_server_protocol::LoginChatGptCompleteNotification;
use codex_app_server_protocol::LoginChatGptResponse;
use codex_app_server_protocol::LoginAccountResponse;
use codex_app_server_protocol::ModelListParams;
use codex_app_server_protocol::ModelListResponse;
use codex_app_server_protocol::NewConversationParams;
use codex_app_server_protocol::NewConversationResponse;
use codex_app_server_protocol::ReadOnlyAccess;
use codex_app_server_protocol::RequestId;
use codex_app_server_protocol::SandboxPolicy;
use codex_app_server_protocol::SendUserMessageParams;
use codex_app_server_protocol::SendUserMessageResponse;
use codex_app_server_protocol::ServerNotification;
use codex_app_server_protocol::ServerRequest;
use codex_app_server_protocol::ThreadItem;
@@ -68,9 +62,7 @@ use codex_app_server_protocol::TurnStartParams;
use codex_app_server_protocol::TurnStartResponse;
use codex_app_server_protocol::TurnStatus;
use codex_app_server_protocol::UserInput as V2UserInput;
use codex_protocol::ThreadId;
use codex_protocol::protocol::Event;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::W3cTraceContext;
use serde::Serialize;
use serde::de::DeserializeOwned;
use serde_json::Value;
@@ -104,6 +96,8 @@ const NOTIFICATIONS_TO_OPT_OUT: &[&str] = &[
"item/reasoning/summaryTextDelta",
"item/reasoning/textDelta",
];
const APP_SERVER_GRACEFUL_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5);
const APP_SERVER_GRACEFUL_SHUTDOWN_POLL_INTERVAL: Duration = Duration::from_millis(100);
/// Minimal launcher that initializes the Codex app-server and logs the handshake.
#[derive(Parser)]
@@ -498,25 +492,16 @@ fn send_message(
config_overrides: &[String],
user_message: String,
) -> Result<()> {
let mut client = CodexClient::connect(endpoint, config_overrides)?;
let initialize = client.initialize()?;
println!("< initialize response: {initialize:?}");
let conversation = client.start_thread()?;
println!("< newConversation response: {conversation:?}");
let subscription = client.add_conversation_listener(&conversation.conversation_id)?;
println!("< addConversationListener response: {subscription:?}");
let send_response = client.send_user_message(&conversation.conversation_id, &user_message)?;
println!("< sendUserMessage response: {send_response:?}");
client.stream_conversation(&conversation.conversation_id)?;
client.remove_thread_listener(subscription.subscription_id)?;
Ok(())
let dynamic_tools = None;
send_message_v2_with_policies(
endpoint,
config_overrides,
user_message,
false,
None,
None,
&dynamic_tools,
)
}
pub fn send_message_v2(
@@ -574,82 +559,85 @@ fn trigger_zsh_fork_multi_cmd_approval(
let default_prompt = "Run this exact command using shell command execution without rewriting or splitting it: /usr/bin/true && /usr/bin/true";
let message = user_message.unwrap_or_else(|| default_prompt.to_string());
let mut client = CodexClient::connect(endpoint, config_overrides)?;
let initialize = client.initialize()?;
println!("< initialize response: {initialize:?}");
with_client(endpoint, config_overrides, |client| {
let initialize = client.initialize()?;
println!("< initialize response: {initialize:?}");
let thread_response = client.thread_start(ThreadStartParams {
dynamic_tools: dynamic_tools.clone(),
..Default::default()
})?;
println!("< thread/start response: {thread_response:?}");
let thread_response = client.thread_start(ThreadStartParams {
dynamic_tools: dynamic_tools.clone(),
..Default::default()
})?;
println!("< thread/start response: {thread_response:?}");
client.command_approval_behavior = match abort_on {
Some(index) => CommandApprovalBehavior::AbortOn(index),
None => CommandApprovalBehavior::AlwaysAccept,
};
client.command_approval_count = 0;
client.command_approval_item_ids.clear();
client.command_execution_statuses.clear();
client.last_turn_status = None;
client.command_approval_behavior = match abort_on {
Some(index) => CommandApprovalBehavior::AbortOn(index),
None => CommandApprovalBehavior::AlwaysAccept,
};
client.command_approval_count = 0;
client.command_approval_item_ids.clear();
client.command_execution_statuses.clear();
client.last_turn_status = None;
let mut turn_params = TurnStartParams {
thread_id: thread_response.thread.id.clone(),
input: vec![V2UserInput::Text {
text: message,
text_elements: Vec::new(),
}],
..Default::default()
};
turn_params.approval_policy = Some(AskForApproval::OnRequest);
turn_params.sandbox_policy = Some(SandboxPolicy::ReadOnly {
access: ReadOnlyAccess::FullAccess,
});
let mut turn_params = TurnStartParams {
thread_id: thread_response.thread.id.clone(),
input: vec![V2UserInput::Text {
text: message,
text_elements: Vec::new(),
}],
..Default::default()
};
turn_params.approval_policy = Some(AskForApproval::OnRequest);
turn_params.sandbox_policy = Some(SandboxPolicy::ReadOnly {
access: ReadOnlyAccess::FullAccess,
});
let turn_response = client.turn_start(turn_params)?;
println!("< turn/start response: {turn_response:?}");
client.stream_turn(&thread_response.thread.id, &turn_response.turn.id)?;
let turn_response = client.turn_start(turn_params)?;
println!("< turn/start response: {turn_response:?}");
client.stream_turn(&thread_response.thread.id, &turn_response.turn.id)?;
if client.command_approval_count < min_approvals {
bail!(
"expected at least {min_approvals} command approvals, got {}",
client.command_approval_count
);
}
let mut approvals_per_item = std::collections::BTreeMap::new();
for item_id in &client.command_approval_item_ids {
*approvals_per_item.entry(item_id.clone()).or_insert(0usize) += 1;
}
let max_approvals_for_one_item = approvals_per_item.values().copied().max().unwrap_or(0);
if max_approvals_for_one_item < min_approvals {
bail!(
"expected at least {min_approvals} approvals for one command item, got max {max_approvals_for_one_item} with map {approvals_per_item:?}"
);
}
let last_command_status = client.command_execution_statuses.last();
if abort_on.is_none() {
if last_command_status != Some(&CommandExecutionStatus::Completed) {
bail!("expected completed command execution, got {last_command_status:?}");
}
if client.last_turn_status != Some(TurnStatus::Completed) {
if client.command_approval_count < min_approvals {
bail!(
"expected completed turn in all-accept flow, got {:?}",
client.last_turn_status
"expected at least {min_approvals} command approvals, got {}",
client.command_approval_count
);
}
} else if last_command_status == Some(&CommandExecutionStatus::Completed) {
bail!(
"expected non-completed command execution in mixed approval/decline flow, got {last_command_status:?}"
let mut approvals_per_item = std::collections::BTreeMap::new();
for item_id in &client.command_approval_item_ids {
*approvals_per_item.entry(item_id.clone()).or_insert(0usize) += 1;
}
let max_approvals_for_one_item = approvals_per_item.values().copied().max().unwrap_or(0);
if max_approvals_for_one_item < min_approvals {
bail!(
"expected at least {min_approvals} approvals for one command item, got max {max_approvals_for_one_item} with map {approvals_per_item:?}"
);
}
let last_command_status = client.command_execution_statuses.last();
if abort_on.is_none() {
if last_command_status != Some(&CommandExecutionStatus::Completed) {
bail!("expected completed command execution, got {last_command_status:?}");
}
if client.last_turn_status != Some(TurnStatus::Completed) {
bail!(
"expected completed turn in all-accept flow, got {:?}",
client.last_turn_status
);
}
} else if last_command_status == Some(&CommandExecutionStatus::Completed) {
bail!(
"expected non-completed command execution in mixed approval/decline flow, got {last_command_status:?}"
);
}
println!(
"[zsh-fork multi-approval summary] approvals={}, approvals_per_item={approvals_per_item:?}, command_statuses={:?}, turn_status={:?}",
client.command_approval_count,
client.command_execution_statuses,
client.last_turn_status
);
}
println!(
"[zsh-fork multi-approval summary] approvals={}, approvals_per_item={approvals_per_item:?}, command_statuses={:?}, turn_status={:?}",
client.command_approval_count, client.command_execution_statuses, client.last_turn_status
);
Ok(())
Ok(())
})
}
fn resume_message_v2(
@@ -661,30 +649,30 @@ fn resume_message_v2(
) -> Result<()> {
ensure_dynamic_tools_unused(dynamic_tools, "resume-message-v2")?;
let mut client = CodexClient::connect(endpoint, config_overrides)?;
with_client(endpoint, config_overrides, |client| {
let initialize = client.initialize()?;
println!("< initialize response: {initialize:?}");
let initialize = client.initialize()?;
println!("< initialize response: {initialize:?}");
let resume_response = client.thread_resume(ThreadResumeParams {
thread_id,
..Default::default()
})?;
println!("< thread/resume response: {resume_response:?}");
let resume_response = client.thread_resume(ThreadResumeParams {
thread_id,
..Default::default()
})?;
println!("< thread/resume response: {resume_response:?}");
let turn_response = client.turn_start(TurnStartParams {
thread_id: resume_response.thread.id.clone(),
input: vec![V2UserInput::Text {
text: user_message,
text_elements: Vec::new(),
}],
..Default::default()
})?;
println!("< turn/start response: {turn_response:?}");
let turn_response = client.turn_start(TurnStartParams {
thread_id: resume_response.thread.id.clone(),
input: vec![V2UserInput::Text {
text: user_message,
text_elements: Vec::new(),
}],
..Default::default()
})?;
println!("< turn/start response: {turn_response:?}");
client.stream_turn(&resume_response.thread.id, &turn_response.turn.id)?;
client.stream_turn(&resume_response.thread.id, &turn_response.turn.id)?;
Ok(())
Ok(())
})
}
fn thread_resume_follow(
@@ -787,34 +775,34 @@ fn send_message_v2_with_policies(
sandbox_policy: Option<SandboxPolicy>,
dynamic_tools: &Option<Vec<DynamicToolSpec>>,
) -> Result<()> {
let mut client = CodexClient::connect(endpoint, config_overrides)?;
with_client(endpoint, config_overrides, |client| {
let initialize = client.initialize_with_experimental_api(experimental_api)?;
println!("< initialize response: {initialize:?}");
let initialize = client.initialize_with_experimental_api(experimental_api)?;
println!("< initialize response: {initialize:?}");
let thread_response = client.thread_start(ThreadStartParams {
dynamic_tools: dynamic_tools.clone(),
..Default::default()
})?;
println!("< thread/start response: {thread_response:?}");
let mut turn_params = TurnStartParams {
thread_id: thread_response.thread.id.clone(),
input: vec![V2UserInput::Text {
text: user_message,
// Test client sends plain text without UI element ranges.
text_elements: Vec::new(),
}],
..Default::default()
};
turn_params.approval_policy = approval_policy;
turn_params.sandbox_policy = sandbox_policy;
let thread_response = client.thread_start(ThreadStartParams {
dynamic_tools: dynamic_tools.clone(),
..Default::default()
})?;
println!("< thread/start response: {thread_response:?}");
let mut turn_params = TurnStartParams {
thread_id: thread_response.thread.id.clone(),
input: vec![V2UserInput::Text {
text: user_message,
// Test client sends plain text without UI element ranges.
text_elements: Vec::new(),
}],
..Default::default()
};
turn_params.approval_policy = approval_policy;
turn_params.sandbox_policy = sandbox_policy;
let turn_response = client.turn_start(turn_params)?;
println!("< turn/start response: {turn_response:?}");
let turn_response = client.turn_start(turn_params)?;
println!("< turn/start response: {turn_response:?}");
client.stream_turn(&thread_response.thread.id, &turn_response.turn.id)?;
client.stream_turn(&thread_response.thread.id, &turn_response.turn.id)?;
Ok(())
Ok(())
})
}
fn send_follow_up_v2(
@@ -824,119 +812,130 @@ fn send_follow_up_v2(
follow_up_message: String,
dynamic_tools: &Option<Vec<DynamicToolSpec>>,
) -> Result<()> {
let mut client = CodexClient::connect(endpoint, config_overrides)?;
with_client(endpoint, config_overrides, |client| {
let initialize = client.initialize()?;
println!("< initialize response: {initialize:?}");
let initialize = client.initialize()?;
println!("< initialize response: {initialize:?}");
let thread_response = client.thread_start(ThreadStartParams {
dynamic_tools: dynamic_tools.clone(),
..Default::default()
})?;
println!("< thread/start response: {thread_response:?}");
let thread_response = client.thread_start(ThreadStartParams {
dynamic_tools: dynamic_tools.clone(),
..Default::default()
})?;
println!("< thread/start response: {thread_response:?}");
let first_turn_params = TurnStartParams {
thread_id: thread_response.thread.id.clone(),
input: vec![V2UserInput::Text {
text: first_message,
// Test client sends plain text without UI element ranges.
text_elements: Vec::new(),
}],
..Default::default()
};
let first_turn_response = client.turn_start(first_turn_params)?;
println!("< turn/start response (initial): {first_turn_response:?}");
client.stream_turn(&thread_response.thread.id, &first_turn_response.turn.id)?;
let first_turn_params = TurnStartParams {
thread_id: thread_response.thread.id.clone(),
input: vec![V2UserInput::Text {
text: first_message,
// Test client sends plain text without UI element ranges.
text_elements: Vec::new(),
}],
..Default::default()
};
let first_turn_response = client.turn_start(first_turn_params)?;
println!("< turn/start response (initial): {first_turn_response:?}");
client.stream_turn(&thread_response.thread.id, &first_turn_response.turn.id)?;
let follow_up_params = TurnStartParams {
thread_id: thread_response.thread.id.clone(),
input: vec![V2UserInput::Text {
text: follow_up_message,
// Test client sends plain text without UI element ranges.
text_elements: Vec::new(),
}],
..Default::default()
};
let follow_up_response = client.turn_start(follow_up_params)?;
println!("< turn/start response (follow-up): {follow_up_response:?}");
client.stream_turn(&thread_response.thread.id, &follow_up_response.turn.id)?;
let follow_up_params = TurnStartParams {
thread_id: thread_response.thread.id.clone(),
input: vec![V2UserInput::Text {
text: follow_up_message,
// Test client sends plain text without UI element ranges.
text_elements: Vec::new(),
}],
..Default::default()
};
let follow_up_response = client.turn_start(follow_up_params)?;
println!("< turn/start response (follow-up): {follow_up_response:?}");
client.stream_turn(&thread_response.thread.id, &follow_up_response.turn.id)?;
Ok(())
Ok(())
})
}
fn test_login(endpoint: &Endpoint, config_overrides: &[String]) -> Result<()> {
let mut client = CodexClient::connect(endpoint, config_overrides)?;
with_client(endpoint, config_overrides, |client| {
let initialize = client.initialize()?;
println!("< initialize response: {initialize:?}");
let initialize = client.initialize()?;
println!("< initialize response: {initialize:?}");
let login_response = client.login_account_chatgpt()?;
println!("< account/login/start response: {login_response:?}");
let LoginAccountResponse::Chatgpt { login_id, auth_url } = login_response else {
bail!("expected chatgpt login response");
};
println!("Open the following URL in your browser to continue:\n{auth_url}");
let login_response = client.login_chat_gpt()?;
println!("< loginChatGpt response: {login_response:?}");
println!(
"Open the following URL in your browser to continue:\n{}",
login_response.auth_url
);
let completion = client.wait_for_account_login_completion(&login_id)?;
println!("< account/login/completed notification: {completion:?}");
let completion = client.wait_for_login_completion(&login_response.login_id)?;
println!("< loginChatGptComplete notification: {completion:?}");
if completion.success {
println!("Login succeeded.");
Ok(())
} else {
bail!(
"login failed: {}",
completion
.error
.as_deref()
.unwrap_or("unknown error from loginChatGptComplete")
);
}
if completion.success {
println!("Login succeeded.");
Ok(())
} else {
bail!(
"login failed: {}",
completion
.error
.as_deref()
.unwrap_or("unknown error from account/login/completed")
);
}
})
}
fn get_account_rate_limits(endpoint: &Endpoint, config_overrides: &[String]) -> Result<()> {
let mut client = CodexClient::connect(endpoint, config_overrides)?;
with_client(endpoint, config_overrides, |client| {
let initialize = client.initialize()?;
println!("< initialize response: {initialize:?}");
let initialize = client.initialize()?;
println!("< initialize response: {initialize:?}");
let response = client.get_account_rate_limits()?;
println!("< account/rateLimits/read response: {response:?}");
let response = client.get_account_rate_limits()?;
println!("< account/rateLimits/read response: {response:?}");
Ok(())
Ok(())
})
}
fn model_list(endpoint: &Endpoint, config_overrides: &[String]) -> Result<()> {
let mut client = CodexClient::connect(endpoint, config_overrides)?;
with_client(endpoint, config_overrides, |client| {
let initialize = client.initialize()?;
println!("< initialize response: {initialize:?}");
let initialize = client.initialize()?;
println!("< initialize response: {initialize:?}");
let response = client.model_list(ModelListParams::default())?;
println!("< model/list response: {response:?}");
let response = client.model_list(ModelListParams::default())?;
println!("< model/list response: {response:?}");
Ok(())
Ok(())
})
}
fn thread_list(endpoint: &Endpoint, config_overrides: &[String], limit: u32) -> Result<()> {
with_client(endpoint, config_overrides, |client| {
let initialize = client.initialize()?;
println!("< initialize response: {initialize:?}");
let response = client.thread_list(ThreadListParams {
cursor: None,
limit: Some(limit),
sort_key: None,
model_providers: None,
source_kinds: None,
archived: None,
cwd: None,
search_term: None,
})?;
println!("< thread/list response: {response:?}");
Ok(())
})
}
fn with_client<T>(
endpoint: &Endpoint,
config_overrides: &[String],
f: impl FnOnce(&mut CodexClient) -> Result<T>,
) -> Result<T> {
let mut client = CodexClient::connect(endpoint, config_overrides)?;
let initialize = client.initialize()?;
println!("< initialize response: {initialize:?}");
let response = client.thread_list(ThreadListParams {
cursor: None,
limit: Some(limit),
sort_key: None,
model_providers: None,
source_kinds: None,
archived: None,
cwd: None,
search_term: None,
})?;
println!("< thread/list response: {response:?}");
Ok(())
let result = f(&mut client);
client.print_trace_summary();
result
}
fn ensure_dynamic_tools_unused(
@@ -993,6 +992,8 @@ struct CodexClient {
command_approval_item_ids: Vec<String>,
command_execution_statuses: Vec<CommandExecutionStatus>,
last_turn_status: Option<TurnStatus>,
trace_id: String,
trace_root_span_id: String,
}
#[derive(Debug, Clone, Copy)]
@@ -1052,6 +1053,8 @@ impl CodexClient {
command_approval_item_ids: Vec::new(),
command_execution_statuses: Vec::new(),
last_turn_status: None,
trace_id: generate_trace_id(),
trace_root_span_id: generate_parent_span_id(),
})
}
@@ -1073,6 +1076,8 @@ impl CodexClient {
command_approval_item_ids: Vec::new(),
command_execution_statuses: Vec::new(),
last_turn_status: None,
trace_id: generate_trace_id(),
trace_root_span_id: generate_parent_span_id(),
})
}
@@ -1117,69 +1122,6 @@ impl CodexClient {
Ok(response)
}
fn start_thread(&mut self) -> Result<NewConversationResponse> {
let request_id = self.request_id();
let request = ClientRequest::NewConversation {
request_id: request_id.clone(),
params: NewConversationParams::default(),
};
self.send_request(request, request_id, "newConversation")
}
fn add_conversation_listener(
&mut self,
conversation_id: &ThreadId,
) -> Result<AddConversationSubscriptionResponse> {
let request_id = self.request_id();
let request = ClientRequest::AddConversationListener {
request_id: request_id.clone(),
params: AddConversationListenerParams {
conversation_id: *conversation_id,
experimental_raw_events: false,
},
};
self.send_request(request, request_id, "addConversationListener")
}
fn remove_thread_listener(&mut self, subscription_id: Uuid) -> Result<()> {
let request_id = self.request_id();
let request = ClientRequest::RemoveConversationListener {
request_id: request_id.clone(),
params: codex_app_server_protocol::RemoveConversationListenerParams { subscription_id },
};
self.send_request::<codex_app_server_protocol::RemoveConversationSubscriptionResponse>(
request,
request_id,
"removeConversationListener",
)?;
Ok(())
}
fn send_user_message(
&mut self,
conversation_id: &ThreadId,
message: &str,
) -> Result<SendUserMessageResponse> {
let request_id = self.request_id();
let request = ClientRequest::SendUserMessage {
request_id: request_id.clone(),
params: SendUserMessageParams {
conversation_id: *conversation_id,
items: vec![InputItem::Text {
text: message.to_string(),
// Test client sends plain text without UI element ranges.
text_elements: Vec::new(),
}],
},
};
self.send_request(request, request_id, "sendUserMessage")
}
fn thread_start(&mut self, params: ThreadStartParams) -> Result<ThreadStartResponse> {
let request_id = self.request_id();
let request = ClientRequest::ThreadStart {
@@ -1210,14 +1152,14 @@ impl CodexClient {
self.send_request(request, request_id, "turn/start")
}
fn login_chat_gpt(&mut self) -> Result<LoginChatGptResponse> {
fn login_account_chatgpt(&mut self) -> Result<LoginAccountResponse> {
let request_id = self.request_id();
let request = ClientRequest::LoginChatGpt {
let request = ClientRequest::LoginAccount {
request_id: request_id.clone(),
params: None,
params: codex_app_server_protocol::LoginAccountParams::Chatgpt,
};
self.send_request(request, request_id, "loginChatGpt")
self.send_request(request, request_id, "account/login/start")
}
fn get_account_rate_limits(&mut self) -> Result<GetAccountRateLimitsResponse> {
@@ -1250,77 +1192,31 @@ impl CodexClient {
self.send_request(request, request_id, "thread/list")
}
fn stream_conversation(&mut self, conversation_id: &ThreadId) -> Result<()> {
loop {
let notification = self.next_notification()?;
if !notification.method.starts_with("codex/event/") {
continue;
}
if let Some(event) = self.extract_event(notification, conversation_id)? {
match &event.msg {
EventMsg::AgentMessage(event) => {
println!("{}", event.message);
}
EventMsg::AgentMessageDelta(event) => {
print!("{}", event.delta);
std::io::stdout().flush().ok();
}
EventMsg::TurnComplete(event) => {
println!("\n[task complete: {event:?}]");
break;
}
EventMsg::TurnAborted(event) => {
println!("\n[turn aborted: {:?}]", event.reason);
break;
}
EventMsg::Error(event) => {
println!("[error] {event:?}");
}
_ => {
println!("[UNKNOWN EVENT] {:?}", event.msg);
}
}
}
}
Ok(())
}
fn wait_for_login_completion(
fn wait_for_account_login_completion(
&mut self,
expected_login_id: &Uuid,
) -> Result<LoginChatGptCompleteNotification> {
expected_login_id: &str,
) -> Result<AccountLoginCompletedNotification> {
loop {
let notification = self.next_notification()?;
if let Ok(server_notification) = ServerNotification::try_from(notification) {
match server_notification {
ServerNotification::LoginChatGptComplete(completion) => {
if &completion.login_id == expected_login_id {
ServerNotification::AccountLoginCompleted(completion) => {
if completion.login_id.as_deref() == Some(expected_login_id) {
return Ok(completion);
}
println!(
"[ignoring loginChatGptComplete for unexpected login_id: {}]",
"[ignoring account/login/completed for unexpected login_id: {:?}]",
completion.login_id
);
}
ServerNotification::AuthStatusChange(status) => {
println!("< authStatusChange notification: {status:?}");
}
ServerNotification::AccountRateLimitsUpdated(snapshot) => {
println!("< accountRateLimitsUpdated notification: {snapshot:?}");
}
ServerNotification::SessionConfigured(_) => {
// SessionConfigured notifications are unrelated to login; skip.
}
_ => {}
}
}
// Not a server notification (likely a conversation event); keep waiting.
}
}
@@ -1394,36 +1290,6 @@ impl CodexClient {
}
}
fn extract_event(
&self,
notification: JSONRPCNotification,
conversation_id: &ThreadId,
) -> Result<Option<Event>> {
let params = notification
.params
.context("event notification missing params")?;
let mut map = match params {
Value::Object(map) => map,
other => bail!("unexpected params shape: {other:?}"),
};
let conversation_value = map
.remove("conversationId")
.context("event missing conversationId")?;
let notification_conversation: ThreadId = serde_json::from_value(conversation_value)
.context("conversationId was not a valid UUID")?;
if &notification_conversation != conversation_id {
return Ok(None);
}
let event_value = Value::Object(map);
let event: Event =
serde_json::from_value(event_value).context("failed to decode event payload")?;
Ok(Some(event))
}
fn send_request<T>(
&mut self,
request: ClientRequest,
@@ -1438,12 +1304,32 @@ impl CodexClient {
}
fn write_request(&mut self, request: &ClientRequest) -> Result<()> {
let request_json = serde_json::to_string(request)?;
let request_pretty = serde_json::to_string_pretty(request)?;
let request = self.jsonrpc_request_with_trace(request)?;
let request_json = serde_json::to_string(&request)?;
let request_pretty = serde_json::to_string_pretty(&request)?;
print_multiline_with_prefix("> ", &request_pretty);
self.write_payload(&request_json)
}
fn jsonrpc_request_with_trace(&self, request: &ClientRequest) -> Result<JSONRPCRequest> {
let request_value = serde_json::to_value(request)?;
let mut request: JSONRPCRequest = serde_json::from_value(request_value)
.context("client request was not a valid JSON-RPC request")?;
request.trace = Some(W3cTraceContext {
traceparent: Some(format!(
"00-{}-{}-01",
self.trace_id, self.trace_root_span_id
)),
tracestate: None,
});
Ok(request)
}
fn print_trace_summary(&self) {
println!("\n[Datadog trace]");
println!("go/trace/{}\n", self.trace_id);
}
fn wait_for_response<T>(&mut self, request_id: RequestId, method: &str) -> Result<T>
where
T: DeserializeOwned,
@@ -1709,6 +1595,15 @@ impl CodexClient {
}
}
fn generate_trace_id() -> String {
Uuid::new_v4().simple().to_string()
}
fn generate_parent_span_id() -> String {
let uuid = Uuid::new_v4().simple().to_string();
uuid[..16].to_string()
}
fn print_multiline_with_prefix(prefix: &str, payload: &str) {
for line in payload.lines() {
println!("{prefix}{line}");
@@ -1728,11 +1623,18 @@ impl Drop for CodexClient {
return;
}
thread::sleep(Duration::from_millis(100));
let deadline = SystemTime::now() + APP_SERVER_GRACEFUL_SHUTDOWN_TIMEOUT;
loop {
if let Ok(Some(status)) = child.try_wait() {
println!("[codex app-server exited: {status}]");
return;
}
if let Ok(Some(status)) = child.try_wait() {
println!("[codex app-server exited: {status}]");
return;
if SystemTime::now() >= deadline {
break;
}
thread::sleep(APP_SERVER_GRACEFUL_SHUTDOWN_POLL_INTERVAL);
}
let _ = child.kill();

View File

@@ -21,6 +21,7 @@ async-trait = { workspace = true }
codex-arg0 = { workspace = true }
codex-cloud-requirements = { workspace = true }
codex-core = { workspace = true }
codex-otel = { workspace = true }
codex-shell-command = { workspace = true }
codex-utils-cli = { workspace = true }
codex-backend-client = { workspace = true }
@@ -31,6 +32,7 @@ codex-protocol = { workspace = true }
codex-app-server-protocol = { workspace = true }
codex-feedback = { workspace = true }
codex-rmcp-client = { workspace = true }
codex-state = { workspace = true }
codex-utils-absolute-path = { workspace = true }
codex-utils-json-to-toml = { workspace = true }
chrono = { workspace = true }
@@ -63,11 +65,8 @@ axum = { workspace = true, default-features = false, features = [
"tokio",
] }
base64 = { workspace = true }
codex-execpolicy = { workspace = true }
core_test_support = { workspace = true }
codex-state = { workspace = true }
codex-utils-cargo-bin = { workspace = true }
os_info = { workspace = true }
pretty_assertions = { workspace = true }
rmcp = { workspace = true, default-features = false, features = [
"server",

View File

@@ -63,7 +63,7 @@ Use the thread APIs to create, list, or archive conversations. Drive a conversat
- Initialize once per connection: Immediately after opening a transport connection, send an `initialize` request with your client metadata, then emit an `initialized` notification. Any other request on that connection before this handshake gets rejected.
- Start (or resume) a thread: Call `thread/start` to open a fresh conversation. The response returns the thread object and youll also get a `thread/started` notification. If youre continuing an existing conversation, call `thread/resume` with its ID instead. If you want to branch from an existing conversation, call `thread/fork` to create a new thread id with copied history.
The returned `thread.ephemeral` flag tells you whether the session is intentionally in-memory only; when it is `true`, `thread.path` is `null`.
- Begin a turn: To send user input, call `turn/start` with the target `threadId` and the user's input. Optional fields let you override model, cwd, sandbox policy, etc. This immediately returns the new turn object and triggers a `turn/started` notification.
- Begin a turn: To send user input, call `turn/start` with the target `threadId` and the user's input. Optional fields let you override model, cwd, sandbox policy, etc. This immediately returns the new turn object. The app-server emits `turn/started` when that turn actually begins running.
- Stream events: After `turn/start`, keep reading JSON-RPC notifications on stdout. Youll see `item/started`, `item/completed`, deltas like `item/agentMessage/delta`, tool progress, etc. These represent streaming model output plus any side effects (commands, tool calls, reasoning notes).
- Finish the turn: When the model is done (or the turn is interrupted via making the `turn/interrupt` call), the server sends `turn/completed` with the final turn state and token usage.
@@ -120,16 +120,17 @@ Example with notification opt-out:
## API Overview
- `thread/start` — create a new thread; emits `thread/started` and auto-subscribes you to turn/item events for that thread.
- `thread/start` — create a new thread; emits `thread/started` (including the current `thread.status`) and auto-subscribes you to turn/item events for that thread.
- `thread/resume` — reopen an existing thread by id so subsequent `turn/start` calls append to it.
- `thread/fork` — fork an existing thread into a new thread id by copying the stored history; emits `thread/started` and auto-subscribes you to turn/item events for the new thread.
- `thread/fork` — fork an existing thread into a new thread id by copying the stored history; emits `thread/started` (including the current `thread.status`) and auto-subscribes you to turn/item events for the new thread.
- `thread/list` — page through stored rollouts; supports cursor-based pagination and optional `modelProviders`, `sourceKinds`, `archived`, `cwd`, and `searchTerm` filters. Each returned `thread` includes `status` (`ThreadStatus`), defaulting to `notLoaded` when the thread is not currently loaded.
- `thread/loaded/list` — list the thread ids currently loaded in memory.
- `thread/read` — read a stored thread by id without resuming it; optionally include turns via `includeTurns`. The returned `thread` includes `status` (`ThreadStatus`), defaulting to `notLoaded` when the thread is not currently loaded.
- `thread/metadata/update` — patch stored thread metadata in sqlite; currently supports updating persisted `gitInfo` fields and returns the refreshed `thread`.
- `thread/status/changed` — notification emitted when a loaded threads status changes (`threadId` + new `status`).
- `thread/archive` — move a threads rollout file into the archived directory; returns `{}` on success and emits `thread/archived`.
- `thread/unsubscribe` — unsubscribe this connection from thread turn/item events. If this was the last subscriber, the server shuts down and unloads the thread, then emits `thread/closed`.
- `thread/name/set` — set or update a threads user-facing name; returns `{}` on success. Thread names are not required to be unique; name lookups resolve to the most recently updated thread.
- `thread/name/set` — set or update a threads user-facing name for either a loaded thread or a persisted rollout; returns `{}` on success. Thread names are not required to be unique; name lookups resolve to the most recently updated thread.
- `thread/unarchive` — move an archived rollout file back into the sessions directory; returns the restored `thread` on success and emits `thread/unarchived`.
- `thread/compact/start` — trigger conversation history compaction for a thread; returns `{}` immediately while progress streams through standard turn/item notifications.
- `thread/backgroundTerminals/clean` — terminate all running background terminals for a thread (experimental; requires `capabilities.experimentalApi`); returns `{}` when the cleanup request is accepted.
@@ -273,10 +274,11 @@ When `nextCursor` is `null`, youve reached the final page.
### Example: Track thread status changes
`thread/status/changed` is emitted whenever a loaded thread's status changes:
`thread/status/changed` is emitted whenever a loaded thread's status changes after it has already been introduced to the client:
- Includes `threadId` and the new `status`.
- Status can be `notLoaded`, `idle`, `systemError`, or `active` (with `activeFlags`; `active` implies running).
- `thread/start`, `thread/fork`, and detached review threads do not emit a separate initial `thread/status/changed`; their `thread/started` notification already carries the current `thread.status`.
```json
{ "method": "thread/status/changed", "params": {
@@ -323,6 +325,34 @@ Use `thread/read` to fetch a stored thread by id without resuming it. Pass `incl
} }
```
### Example: Update stored thread metadata
Use `thread/metadata/update` to patch sqlite-backed metadata for a thread without resuming it. Today this supports persisted `gitInfo`; omitted fields are left unchanged, while explicit `null` clears a stored value.
```json
{ "method": "thread/metadata/update", "id": 24, "params": {
"threadId": "thr_123",
"gitInfo": { "branch": "feature/sidebar-pr" }
} }
{ "id": 24, "result": {
"thread": {
"id": "thr_123",
"gitInfo": { "sha": null, "branch": "feature/sidebar-pr", "originUrl": null }
}
} }
{ "method": "thread/metadata/update", "id": 25, "params": {
"threadId": "thr_123",
"gitInfo": { "branch": null }
} }
{ "id": 25, "result": {
"thread": {
"id": "thr_123",
"gitInfo": null
}
} }
```
### Example: Archive a thread
Use `thread/archive` to move the persisted rollout (stored as a JSONL file on disk) into the archived sessions directory.
@@ -620,7 +650,7 @@ Because audio is intentionally separate from `ThreadItem`, clients can opt out o
### Turn events
The app-server streams JSON-RPC notifications while a turn is running. Each turn starts with `turn/started` (initial `turn`) and ends with `turn/completed` (final `turn` status). Token usage events stream separately via `thread/tokenUsage/updated`. Clients subscribe to the events they care about, rendering each item incrementally as updates arrive. The per-item lifecycle is always: `item/started` → zero or more item-specific deltas → `item/completed`.
The app-server streams JSON-RPC notifications while a turn is running. Each turn emits `turn/started` when it begins running and ends with `turn/completed` (final `turn` status). Token usage events stream separately via `thread/tokenUsage/updated`. Clients subscribe to the events they care about, rendering each item incrementally as updates arrive. The per-item lifecycle is always: `item/started` → zero or more item-specific deltas → `item/completed`.
- `turn/started``{ turn }` with the turn id, empty `items`, and `status: "inProgress"`.
- `turn/completed``{ turn }` where `turn.status` is `completed`, `interrupted`, or `failed`; failures carry `{ error: { message, codexErrorInfo?, additionalDetails? } }`.

View File

@@ -0,0 +1,101 @@
use crate::message_processor::ConnectionSessionState;
use crate::outgoing_message::ConnectionId;
use crate::transport::AppServerTransport;
use codex_app_server_protocol::InitializeParams;
use codex_app_server_protocol::JSONRPCRequest;
use codex_otel::set_parent_from_context;
use codex_otel::set_parent_from_w3c_trace_context;
use codex_otel::traceparent_context_from_env;
use codex_protocol::protocol::W3cTraceContext;
use tracing::Span;
use tracing::field;
use tracing::info_span;
pub(crate) fn request_span(
request: &JSONRPCRequest,
transport: AppServerTransport,
connection_id: ConnectionId,
session: &ConnectionSessionState,
) -> Span {
let span = info_span!(
"app_server.request",
otel.kind = "server",
otel.name = request.method.as_str(),
rpc.system = "jsonrpc",
rpc.method = request.method.as_str(),
rpc.transport = transport_name(transport),
rpc.request_id = ?request.id,
app_server.connection_id = ?connection_id,
app_server.api_version = "v2",
app_server.client_name = field::Empty,
app_server.client_version = field::Empty,
);
let initialize_client_info = initialize_client_info(request);
if let Some(client_name) = client_name(initialize_client_info.as_ref(), session) {
span.record("app_server.client_name", client_name);
}
if let Some(client_version) = client_version(initialize_client_info.as_ref(), session) {
span.record("app_server.client_version", client_version);
}
if let Some(traceparent) = request
.trace
.as_ref()
.and_then(|trace| trace.traceparent.as_deref())
{
let trace = W3cTraceContext {
traceparent: Some(traceparent.to_string()),
tracestate: request
.trace
.as_ref()
.and_then(|value| value.tracestate.clone()),
};
if !set_parent_from_w3c_trace_context(&span, &trace) {
tracing::warn!(
rpc_method = request.method.as_str(),
rpc_request_id = ?request.id,
"ignoring invalid inbound request trace carrier"
);
}
} else if let Some(context) = traceparent_context_from_env() {
set_parent_from_context(&span, context);
}
span
}
fn transport_name(transport: AppServerTransport) -> &'static str {
match transport {
AppServerTransport::Stdio => "stdio",
AppServerTransport::WebSocket { .. } => "websocket",
}
}
fn client_name<'a>(
initialize_client_info: Option<&'a InitializeParams>,
session: &'a ConnectionSessionState,
) -> Option<&'a str> {
if let Some(params) = initialize_client_info {
return Some(params.client_info.name.as_str());
}
session.app_server_client_name.as_deref()
}
fn client_version<'a>(
initialize_client_info: Option<&'a InitializeParams>,
session: &'a ConnectionSessionState,
) -> Option<&'a str> {
if let Some(params) = initialize_client_info {
return Some(params.client_info.version.as_str());
}
session.client_version.as_deref()
}
fn initialize_client_info(request: &JSONRPCRequest) -> Option<InitializeParams> {
if request.method != "initialize" {
return None;
}
let params = request.params.clone()?;
serde_json::from_value(params).ok()
}

Some files were not shown because too many files have changed in this diff Show More