Compare commits

..

35 Commits

Author SHA1 Message Date
xli-oai
b1ec596a53 Parallelize skills list cwd loading 2026-05-06 15:35:44 -07:00
xli-oai
bdf075769d app-server: log skills list stage timings 2026-05-06 12:31:24 -07:00
xli-oai
d923e47cb9 app-server: expose plugin timing log under desktop defaults 2026-05-06 10:59:20 -07:00
xli-oai
34f09a0ca1 app-server: log plugin list stage timings 2026-05-06 10:49:57 -07:00
xli-oai
ed7a129ecc Allow config reads to share config queue 2026-05-06 03:34:22 -07:00
xli-oai
0029bf63be app-server: allow shared config reads 2026-05-06 03:25:34 -07:00
jif-oai
06e5dfa4dd feat: return session ID from thread/fork (#21332)
## Why

`thread/start` and `thread/resume` already return `sessionId`, but
`thread/fork` only returned the new thread. That left clients to infer
the forked thread's session identity from `thread.id`, which kept the
new `session_id` / `thread_id` split implicit at one lifecycle boundary.
Follow-up to #20437.

## What changed

- Add `sessionId` to `ThreadForkResponse`.
- Populate it from the forked session configuration.
- Regenerate the v2 JSON/TypeScript schema fixtures and update the
app-server docs/example.
- Extend the fork integration test to assert the returned `sessionId`.

## Verification

- Added coverage in `thread_fork_creates_new_thread_and_emits_started`
for the new response field.
2026-05-06 12:04:27 +02:00
jif-oai
fe24a180ab feat: include thread ID in MCP turn metadata (#21329)
## Why

MCP tool calls already include `session_id` in `x-codex-turn-metadata`,
but descendant threads intentionally share that value with the root
thread. Consumers that need to correlate work at the concrete thread
level also need the current `thread_id`.

## What changed

- add `thread_id` to `x-codex-turn-metadata` while preserving
`session_id` as the shared session identity
- thread the two identities separately through normal turns and spawned
review threads
- add regression coverage for resumed sessions, reserved metadata
fields, and deferred MCP tool calls

## Verification

- added focused coverage in `core/src/session/tests.rs`,
`core/src/turn_metadata_tests.rs`, and `core/tests/suite/search_tool.rs`
2026-05-06 11:36:15 +02:00
jif-oai
b5e965e1d7 test: isolate app-server-client in-process test state (#21328)
## Why

The in-process `app-server-client` tests were still building their
configs from the ambient `codex_home` and letting the embedded app
server create its own state DB when `state_db` was absent. That matters
because in-process startup falls back to
`init_state_db_from_config(...)` in that case, so tests can otherwise
share persisted state instead of getting isolated fixtures:
[`app-server/src/in_process.rs`](a98623511b/codex-rs/app-server/src/in_process.rs (L368-L373)).

## What changed

- Give each in-process test client its own temporary `codex_home`.
- Initialize the matching state DB from that per-client config and pass
it into the client explicitly.
- Keep the temp directory alive for the lifetime of the test client
through a small `TestClient` wrapper.
- Add `tempfile` as a dev dependency for the new harness.

The updated setup lives in
[`app-server-client/src/lib.rs`](35c1133d45/codex-rs/app-server-client/src/lib.rs (L982-L1055)).

## Testing

- Existing `codex-app-server-client` tests continue to exercise the
updated in-process client path through the isolated helper.
2026-05-06 09:21:22 +00:00
jif-oai
a98623511b feat: add session_id (#20437)
## Summary

Related to
https://openai.slack.com/archives/C095U48JNL9/p1777537279707449
TLDR:
We update the meaning of session ids and thread ids:
* thread_id stays as now
* session_id become a shared id between every thread under a /root
thread (i.e. every sub-agent share the same session id)

This PR introduces an explicit `SessionId` and threads it through the
protocol/client boundary so `session_id` and `thread_id` can diverge
when they need to, while preserving compatibility for older serialized
`session_configured` events.

---------

Co-authored-by: Codex <noreply@openai.com>
2026-05-06 10:48:37 +02:00
Matthew Zeng
f9a907aebe Support Codex Apps auth elicitations (#19193)
## Summary

- request URL-mode MCP elicitations when Codex Apps tool calls fail with
connector auth metadata
- route Codex Apps auth URL elicitations into the TUI app-link flow

## Test plan

- `just fmt`
- `cargo test -p codex-core mcp_tool_call::tests`
- `cargo test -p codex-mcp`
- `cargo test -p codex-tui bottom_pane::app_link_view::tests`
- `just fix -p codex-core`
- `just fix -p codex-mcp`
- `just fix -p codex-tui`

Also attempted broader local runs:

- `cargo test -p codex-core` fails in unrelated
config/request-permission/proxy-sensitive tests under the current Codex
Desktop environment.
- `cargo test -p codex-tui` fails in unrelated status
snapshots/trust-default tests because the ambient environment renders
workspace-write/network permission defaults.
2026-05-06 07:18:00 +00:00
Michael Bolin
22326e263c release: bundle bwrap with Linux codex DotSlash artifact (#21312)
## Why

#21255 changed the Linux sandbox fallback so Codex can use a bundled
`codex-resources/bwrap` executable when no suitable system `bwrap` is
available. That lookup is relative to the native Codex executable
returned by
`std::env::current_exe()`, as implemented in
[`bundled_bwrap.rs`](9766d3d51c/codex-rs/linux-sandbox/src/bundled_bwrap.rs (L83-L93)).

The release already publishes a separate `bwrap` DotSlash output, but
the Linux `codex` DotSlash output still pointed at a single-binary
`.zst` payload. Running the `codex` DotSlash manifest only materializes
the native `codex` executable; it does not also create sibling files
from the separate `bwrap` manifest. The fallback path therefore needs
the Linux `codex` DotSlash artifact itself to include the real `bwrap`
executable at `codex-resources/bwrap`.

## What changed

- stage a Linux primary `codex-<target>-bundle.tar.zst` release artifact
containing `codex` and `codex-resources/bwrap`
- point the Linux `codex` DotSlash outputs at that bundle tarball
- leave the standalone `bwrap` DotSlash output in place for consumers
that want to fetch `bwrap` directly

## Verification

- `jq . .github/dotslash-config.json`
- Ruby YAML parse of `.github/workflows/rust-release.yml`
2026-05-05 23:33:13 -07:00
viyatb-oai
9766d3d51c fix(bwrap): emit libcap after standalone archive (#21285)
## Why

#21255 added the standalone `codex-bwrap` binary. In the Cargo build,
[`pkg_config::probe("libcap")`](a736cb55a2/codex-rs/bwrap/build.rs (L37-L39))
emits `-lcap` before
[`cc::Build::compile("standalone_bwrap")`](a736cb55a2/codex-rs/bwrap/build.rs (L50-L67))
adds the static bwrap archive. The Linux musl link then sees `-lcap
-lstandalone_bwrap`; because static archives are resolved left-to-right,
`cap_from_name` is still undefined once `standalone_bwrap` introduces
that reference.

The musl setup already builds `libcap.a` and exposes it through
[`libcap.pc`](a736cb55a2/.github/scripts/install-musl-build-tools.sh (L78-L88)),
so the failure is link ordering rather than a missing dependency.

## What changed

- probe `libcap` with `cargo_metadata(false)` so `pkg-config` does not
emit its link flags early
- emit the discovered `libcap` search paths and libraries after
`standalone_bwrap` is compiled, preserving the needed static-link order

## Verification

- `cargo test -p codex-bwrap`
- `cargo clippy -p codex-bwrap --all-targets`

The affected Linux musl release link is exercised by CI, which is the
path this fix targets.
2026-05-05 22:22:01 -07:00
Matthew Zeng
41505bcea2 [mcp] Return Accept early per feedback. (#21277)
- [x] Return Accept early when auto_deny is enabled per feedback.
2026-05-05 21:23:42 -07:00
aaronl-openai
9f06d171e2 Preserve session MCP config on refresh (#21055)
# Overview
MCP refreshes were rebuilding active threads from fresh disk-backed
config only, which dropped thread-start session overlays such as
app-injected MCP servers. This keeps refreshes current with disk config
while preserving the thread-local config that only the active thread
knows about.

# Changes
- Rebuild refreshed config per active thread using that thread's current
`cwd`, rather than fanning out one app-server config to every thread.
- Preserve each thread's `SessionFlags` layer while replacing reloadable
config layers with freshly loaded config, then derive the MCP refresh
payload from the rebuilt result.
- Move MCP refresh orchestration into app-server so manual refreshes
fail loudly while background refreshes remain best-effort, and route
plugin-triggered refreshes through the same per-thread reload path.
- Add regression coverage for session overlays, fresh project config,
plugin-derived MCP config, current requirements, and strict vs
best-effort refresh behavior.

# Verification
- Passed focused Rust coverage for the thread-config rebuild behavior
and deferred MCP refresh flow, plus `cargo test -p codex-app-server
--lib`.
- Verified end to end in the Codex dev app against the locally built
CLI: registered an MCP via thread config, verified that it could be used
successfully before refresh, manually triggered MCP refresh, and
verified that it continued to be available afterward.
2026-05-05 21:09:28 -07:00
Andrei Eternal
8ef31894dc app-server: align dynamic tool identifiers with Responses API (#20724)
## Why

Codex currently accepts dynamic tool names and namespaces that the
upstream Responses function-tool path does not actually support. In
practice, that means app-server can register a dynamic tool successfully
and only discover later that the LLM-facing tool contract will reject or
mishandle it.

This PR tightens the app-server-side dynamic tool contract to match the
Responses API before we stack dynamic tool hook support on top of it.

## What changed

- validate dynamic tool `name` against the Responses function-tool
identifier contract: `^[a-zA-Z0-9_-]+$`, length `1..128`
- validate dynamic tool `namespace` the same way, with the Responses
namespace length limit `1..64`
- reject namespaces that collide with the always-reserved Responses
runtime namespaces such as `functions`, `multi_tool_use`, `file_search`,
`web`, `browser`, `image_gen`, `computer`, `container`, `terminal`,
`python`, `python_user_visible`, `api_tool`, `tool_search`, and
`submodel_delegator`
- escape invalid identifiers in error messages so control characters do
not spill raw into logs or client-visible error text
- document the tightened dynamic tool identifier contract in
`codex-rs/app-server/README.md`
- add both unit coverage for the validator and an app-server integration
test that rejects a `thread/start` request with Responses-incompatible
dynamic tool identifiers

## Verification

- `cargo test -p codex-app-server validate_dynamic_tools_`
- `cargo test -p codex-app-server --test all
thread_start_rejects_dynamic_tools_not_supported_by_responses`
2026-05-05 21:05:00 -07:00
xl-openai
5119680f85 feat: Add plugin share access controls (#21124)
Extends `plugin/share/save` to accept optional discoverability and
shareTargets while uploading plugin contents, and adds
`plugin/share/updateTargets` for share-only target updates without
re-uploading.
2026-05-05 20:14:18 -07:00
rhan-oai
b3d4f1a9f0 [codex-analytics] rework thread_source for thread analytics (#20949)
## Summary
- make `thread_source` an explicit optional thread-level field on
`thread/start`, `thread/fork`, and returned thread payloads
- persist `thread_source` in rollout/session metadata so resumed live
threads retain the original value
- replace the old best-effort `session_source` -> `thread_source`
mapping with an explicit caller-supplied analytics classification

## Why
Before this change, analytics `thread_source` was populated by a
best-effort mapping from `session_source`. `session_source` describes
the runtime/client surface, not the actual thread-level origin, so that
projection was not accurate enough to distinguish cases such as `user`,
`subagent`, `memory_consolidation`, and future thread origins reliably.

Making `thread_source` explicit keeps one thread-level analytics field
while letting callers provide the real classification directly instead
of recovering it indirectly from `session_source`.

## Impact
For new analytics events, `thread_source` now reflects the explicit
thread-level classification supplied by the caller rather than an
inferred value derived from `session_source`. Existing protocol fields
remain optional; callers that omit `threadSource` now produce `null`
instead of a best-effort inferred value.

## Validation
- `just write-app-server-schema`
- `cargo test -p codex-analytics -p codex-core -p
codex-app-server-protocol --no-run`
- `cargo test -p codex-app-server-protocol
generated_ts_optional_nullable_fields_only_in_params`
- `cargo test -p codex-analytics
thread_initialized_event_serializes_expected_shape`
- `cargo test -p codex-core
resume_stopped_thread_from_rollout_preserves_thread_source`
2026-05-06 02:12:31 +00:00
Abdulrahman Alfozan
94db03d5af Expose plugin manifest keywords in app server (#21271)
## Summary
- Add plugin manifest keywords to core plugin marketplace/detail models
- Expose keywords on app-server v2 PluginSummary and generated
schema/types
- Populate keywords in plugin/list and plugin/read responses for local
plugins

Depends on https://github.com/openai/openai/pull/891087

## Validation
- just fmt
- just write-app-server-schema
- cargo test -p codex-app-server-protocol
- cargo test -p codex-core-plugins
- cargo test -p codex-app-server
plugin_list_keeps_valid_marketplaces_when_another_marketplace_fails_to_load
- cargo test -p codex-app-server
plugin_read_returns_plugin_details_with_bundle_contents
2026-05-06 02:09:05 +00:00
pakrym-oai
136e442e95 [codex] Remove legacy ListSkills op (#21282)
## Why

`skills/list` is already exposed through app-server v2 and covered by
the app-server test suite. Keeping the separate core `Op::ListSkills`
path leaves a duplicate legacy protocol surface that no longer needs to
be maintained.

## What Changed

- Removed `Op::ListSkills` and `EventMsg::ListSkillsResponse` from the
core protocol.
- Deleted the corresponding core session handler and stale core
integration tests.
- Removed rollout/MCP ignore branches and protocol v1 docs references
for the deleted event/op.
- Left app-server `skills/list` and its existing coverage intact.

## Validation

- `cargo test -p codex-protocol`
- `cargo test -p codex-core --test all suite::skills`
- `cargo check -p codex-mcp-server -p codex-rollout -p
codex-rollout-trace`
- `just fix -p codex-core`
2026-05-05 18:58:18 -07:00
pakrym-oai
024118625e [codex] Remove unused ListModels op (#21276)
## Why

The core protocol still exposed a `ListModels` submission op even though
no client sends it and the core submission loop treated it as an ignored
unknown op. Keeping the dead variant made the protocol surface look
supported while the active model listing API is the app-server
`model/list` JSON-RPC request.

## What Changed

- Removed the unused `Op::ListModels` variant from `codex-rs/protocol`.
- Removed its `Op::kind()` mapping.

The existing app-server `model/list` endpoint is unchanged.

## Verification

- `cargo test -p codex-protocol`
2026-05-06 01:57:17 +00:00
Michael Bolin
a736cb55a2 release/npm: bundle standalone bwrap on Linux (#21257) 2026-05-05 18:21:52 -07:00
iceweasel-oai
db22c91e61 Share Git safe-command logic on Windows (#21275)
## Why

BUGB-15601 showed that the Windows safe-command path had drifted from
the generic Git classifier. The Windows-specific Git parser could
classify a PowerShell-wrapped `git` command as safe as soon as it found
a safelisted subcommand, without applying the generic checks for unsafe
subcommand options such as `--output`, `--ext-diff`, `--textconv`,
`--paginate`, or `cat-file --filters`.

The generic classifier already models the Git command boundary and the
read-only argument checks more carefully, so Windows should reuse that
logic instead of maintaining a smaller parallel parser.

## What Changed

- Extracted the existing generic Git classification logic into
`is_safe_git_command`.
- Updated `windows_safe_commands.rs` to call that shared helper for
parsed PowerShell `git` commands.
- Removed the Windows-only Git subcommand safelist, including the
`cat-file` allowance that was part of the reported bypass.
- Added a Windows regression test that keeps PowerShell-wrapped Git
commands with side-effecting options classified unsafe.
- Made the full-path PowerShell test discover the installed PowerShell
executable instead of depending on one hard-coded `pwsh.exe` path.

## Verification

- `cargo test -p codex-shell-command
rejects_git_subcommand_options_with_side_effects`
- `cargo test -p codex-shell-command
git_global_override_flags_are_not_safe`
- `cargo test -p codex-shell-command
windows_powershell_full_path_is_safe -- --nocapture`

Co-authored-by: Codex <codex@openai.com>
2026-05-05 17:49:42 -07:00
mchen-oai
794c240f25 Add model and reasoning effort to MCP turn metadata (#21219)
## Why
- Similar change as https://github.com/openai/codex/pull/19473.
- Without change: MCP tool calls receive
`_meta["x-codex-turn-metadata"]` with `session_id`, `turn_id`, and
`turn_started_at_unix_ms`.
- Issue: MCP servers may want the model and reasoning effort to better
understand tool-call behavior and latency relative to turn start.

## What Changed
- With change: MCP turn metadata now includes `model` and
`reasoning_effort`, propagated in `_meta["x-codex-turn-metadata"]`.
- Normal `/responses` turn metadata headers are unchanged.

## Verification
- `codex-rs/core/src/mcp_tool_call_tests.rs`
- `codex-rs/core/src/turn_metadata_tests.rs`
- `codex-rs/core/tests/suite/search_tool.rs`
2026-05-05 17:37:48 -07:00
pakrym-oai
2c1a361a2e [codex] Move thread naming to app server (#21260)
## Why

Thread names are app-server metadata now, backed by the thread store and
sqlite state database. Keeping a core `SetThreadName` op plus a rollout
`thread_name_updated` event made rename persistence live in the wrong
layer and required historical replay support for an event that new
app-server flows should not write.

## What changed

- Removed `Op::SetThreadName` and `EventMsg::ThreadNameUpdated` from the
core protocol and deleted the core handler path that appended rename
events to rollouts.
- Updated app-server `thread/name/set` so both loaded and unloaded
threads write through thread-store metadata and app-server emits
`thread/name/updated` notifications.
- Updated local thread-store name metadata updates to write sqlite title
metadata and the legacy thread-name index without appending rollout
events.
- Removed state extraction and rollout handling for the deleted
thread-name event.

## Validation

- `cargo test -p codex-app-server thread_name_updated_broadcasts`
- `cargo test -p codex-app-server
thread_name_set_is_reflected_in_read_list_and_resume`
- `cargo test -p codex-thread-store
update_thread_metadata_sets_name_on_active_rollout_and_indexes_name`
- `cargo test -p codex-state`
- `cargo check -p codex-mcp-server -p codex-rollout-trace`
- `just fix -p codex-app-server -p codex-thread-store -p codex-state -p
codex-mcp-server -p codex-rollout-trace`

## Docs

No external documentation update is expected for this internal ownership
change.
2026-05-05 17:16:06 -07:00
Michael Bolin
3ec18a2c0a release: publish standalone bwrap artifacts (#21256)
**Summary**
- Build Linux `bwrap` before the main release binaries.
- Export the release `bwrap` SHA-256 as `CODEX_BWRAP_SHA256` so the
Codex binary can verify the bundled fallback.
- Sign, stage, and upload `bwrap` alongside the primary Linux release
artifacts.

**Verification**
- YAML parse check for `.github/workflows/rust-release.yml`











---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/21256).
* #21257
* __->__ #21256
2026-05-05 17:15:46 -07:00
Michael Bolin
26f355b67b linux-sandbox: use standalone bundled bwrap (#21255)
**Summary**
- Add `codex-bwrap`, a standalone `bwrap` binary built from the existing
vendored bubblewrap sources.
- Remove the linked vendored bwrap path from `codex-linux-sandbox`;
runtime now prefers system `bwrap` and falls back to bundled
`codex-resources/bwrap`.
- Add bundled SHA-256 verification with missing/all-zero digest as the
dev-mode skip value, then exec the verified file through
`/proc/self/fd`.
- Keep `launcher.rs` focused on choosing and dispatching the preferred
launcher. Bundled lookup, digest verification, and bundled exec now live
in `linux-sandbox/src/bundled_bwrap.rs`; Bazel runfiles lookup lives in
`linux-sandbox/src/bazel_bwrap.rs`; shared argv/fd exec helpers live in
`linux-sandbox/src/exec_util.rs`.
- Teach Bazel tests to surface the Bazel-built `//codex-rs/bwrap:bwrap`
through `CARGO_BIN_EXE_bwrap`; `codex-linux-sandbox` only honors that
fallback in debug Bazel runfiles environments so release/user runtime
lookup stays tied to `codex-resources/bwrap`.
- Allow `codex-exec-server` filesystem helpers to preserve just the
Bazel bwrap/runfiles variables they need in debug Bazel builds, since
those helpers intentionally rebuild a small environment before spawning
`codex-linux-sandbox`.
- Verify the Bazel bwrap target in Linux release CI with a build-only
check. Running `bwrap --version` is too strong for GitHub runners
because bubblewrap still attempts namespace setup there.

**Verification**
- Latest update: `cargo test -p codex-linux-sandbox`
- Latest update: `just fix -p codex-linux-sandbox`
- `cargo check --target x86_64-unknown-linux-gnu -p codex-linux-sandbox`
could not run locally because this macOS machine does not have
`x86_64-linux-gnu-gcc`; GitHub Linux Bazel CI is expected to cover the
Linux-only modules.
- Earlier in this PR: `cargo test -p codex-bwrap`
- Earlier in this PR: `cargo test -p codex-exec-server`
- Earlier in this PR: `cargo check --release -p codex-exec-server`
- Earlier in this PR: `just fix -p codex-linux-sandbox -p
codex-exec-server`
- Earlier in this PR: `bazel test --nobuild
//codex-rs/linux-sandbox:linux-sandbox-all-test
//codex-rs/core:core-all-test
//codex-rs/exec-server:exec-server-file_system-test
//codex-rs/app-server:app-server-all-test` (analysis completed; Bazel
then refuses to run tests under `--nobuild`)
- Earlier in this PR: `bazel build --nobuild //codex-rs/bwrap:bwrap`
- Prior to this update: `just bazel-lock-update`, `just
bazel-lock-check`, and YAML parse check for
`.github/workflows/bazel.yml`


---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/21255).
* #21257
* #21256
* __->__ #21255
2026-05-05 17:14:29 -07:00
Channing Conger
03d3403a41 ci: trigger rusty-v8 releases from tags (#21259)
Swap to tag based releasing and allow tags of type `rusty-v8-v*.*.*`
2026-05-05 16:56:43 -07:00
Owen Lin
d7de4dd3ac chore(app-server-protocol): split v2 API definitions into modules (#21251)
## Why

`codex-rs/app-server-protocol/src/protocol/v2.rs` had grown into a
single ~12k-line definition file for the entire app-server v2 API.

This is purely a mechanical refactor to break up the monolithic `v2.rs`
file that contains all app-server API v2 types into more modular files,
grouped by resource (e.g. account, thread, turn, etc.).

`just write-app-server-schema` shows no real changes, so we can be sure
that this is purely an internal organizational change.

## What changed

- Replaced the monolithic `protocol/v2.rs` with a `protocol/v2/` module
tree and a small `mod.rs` that only declares and reexports modules.
- Grouped v2 API definitions by conceptual owner, including `account`,
`apps`, `collaboration_mode`, `command_exec`, `config`, `device_key`,
`experimental_feature`, `feedback`, `fs`, `hook`, `item`, `mcp`,
`model`, `notification`, `permissions`, `plugin`, `process`, `realtime`,
`review`, `thread`, `thread_data`, `turn`, and `windows_sandbox`.
- Moved v2 tests into `protocol/v2/tests.rs` so `mod.rs` stays small.
- Kept shared protocol helpers in `protocol/v2/shared.rs`, including the
enum mirroring macro and common cross-resource types.
- Co-located resource-specific notifications and server-request payloads
with the modules that own those resources.
- Regenerated app-server protocol schema fixtures. The schema diffs are
non-semantic newline-only changes after the refactor.

## Verification

- `cargo check -p codex-app-server-protocol`
- `cargo test -p codex-app-server-protocol`
- `just write-app-server-schema`
2026-05-05 16:46:51 -07:00
Michael Bolin
332b8b2c74 fix build (#21261)
I believe a merge race in https://github.com/openai/codex/pull/20689
broke the build, so this is a quick fix.

`cargo check --tests` passed locally.
2026-05-05 16:02:06 -07:00
Tom
ee02cf26d6 codex: use ThreadStore history for core review forks (#20577)
- fork loaded parent threads from `ThreadStore` history in core agent
control paths
- migrate guardian review fork history to loaded session history instead
of rereading rollout files

## Verification
- `cargo test -p codex-core spawn_agent_fork`
2026-05-05 15:25:19 -07:00
Michael Zeng
d0f9d5eba2 Add cloud executor registration to exec-server (#19575)
## Summary
This PR adds the first `codex-rs` milestone for remote-exec e2e: a local
`codex exec-server` can now register itself with
`codex-cloud-environments` and attach to the returned rendezvous
websocket.

At a high level, `codex exec-server --cloud ...` now:
- loads ChatGPT auth from normal Codex config
- registers an executor with `codex-cloud-environments`
- receives a signed rendezvous websocket URL
- serves the existing exec-server JSON-RPC protocol over that websocket

## What Changed
- Added `--cloud`, `--cloud-base-url`, `--cloud-environment-id`, and
`--cloud-name` to `codex exec-server`
- Added a new `exec-server/src/cloud.rs` module that handles:
  - registration requests
  - auth/header setup
  - bounded auth retry on `401/403`
  - reconnect/backoff after websocket disconnects
- Reused the existing `ConnectionProcessor` / `ExecServerHandler` path
so cloud mode serves the same exec/filesystem RPC surface as local
websocket mode
- Added cloud-specific error variants and minimal docs for the new mode

## Testing
Manual e2e test that fully goes through exec server flow with our codex
cloud agent as orchestrator
2026-05-05 22:01:48 +00:00
Rasmus Rygaard
7e310bc7f3 Inject state DB, agent graph store (#20689)
## Why

We want the agent graph store to be passed down the stack as a real
dependency, the same way we already treat the thread store.

This will let us inject the agent graph store as a real dependency and
support implementations other than the local SQLite-backed one. Right
now most code instantiates a state DB and an agent graph store
just-in-time. Ideally, we would not depend on the state DB directly but
only read through the higher-level interfaces.

This change makes the dependency boundaries explicit and moves state DB
initialization to process bootstrap instead of hiding it inside local
store implementations.

## What changed

- `ThreadManager` now requires a `StateDbHandle` and an
`AgentGraphStore` at construction time instead of treating them as
optional internals.
- The local store constructors no longer lazily initialize SQLite.
Callers now initialize the state DB once per process and use that shared
handle to build:
  - `LocalThreadStore`
  - `LocalAgentGraphStore`
- App bootstraps (`app-server`, `mcp-server`, `prompt_debug`, and the
thread-manager sample) now initialize the state DB up front and inject
the resulting handle down the stack.
- `app-server` now consistently uses its process-scoped state DB handle
instead of reopening SQLite or trying to recover it from loaded threads.
- Device-key storage now reuses the shared state DB handle instead of
maintaining its own lazy opener.
- The thread archive / descendant traversal paths now use the injected
`AgentGraphStore` instead of reaching through local
thread-store-specific state.

## Verification

- `cargo check -p codex-core -p codex-thread-store -p codex-app-server
-p codex-mcp-server -p codex-thread-manager-sample --tests`
- `cargo test -p codex-thread-store`
- `cargo test -p codex-core
thread_manager_accepts_separate_agent_graph_store_and_thread_store --
--nocapture`
- `cargo test -p codex-app-server
thread_archive_archives_spawned_descendants -- --nocapture`
2026-05-05 21:45:29 +00:00
Channing Conger
36460387ec Enable V8 sandboxing for source-built builds (#21146)
## Summary

This is the first PR in the V8 in-process sandboxing rollout.

It adds the build-system and Rust feature plumbing needed to support
sandboxed V8 builds, then enables sandboxing by default for the
source-built Bazel V8 path that we control directly. It deliberately
keeps the published `rusty_v8` artifact workflows on their current
non-sandboxed contract so this PR can land and ship independently before
we change any released artifacts.

## Rollout plan

- [x] **PR 1: land sandbox plumbing and default source-built Bazel V8 to
sandboxed mode**

- [ ] **PR 2: publish sandbox-enabled release artifacts and add
compatibility validation**
- Produce sandboxed artifact pairs for every released Cargo target that
does not already use the source-built Bazel path.
- Add CI coverage that consumes those sandboxed artifacts and verifies:
    - `codex-v8-poc` reports sandbox enabled
    - `codex-code-mode` builds/tests against the sandboxed path

- [ ] **PR 3: switch release consumers to sandboxed artifacts by
default**
  - Update released artifact selectors/checksums.
- Enable the Rust `v8_enable_sandbox` feature in the default release
path.
- Make the sandboxed artifact family the normal path for published
builds.

- [ ] **PR 4: remove rollout-only compatibility paths**
- Remove the temporary non-sandbox release compatibility config once the
new default has shipped and baked.
  - Keep the invariant tests permanently.
2026-05-05 14:36:37 -07:00
Felipe Coury
bb2257e3f5 [codex] fix TUI turn items view fixtures (#21243)
## Summary

Adds the required `items_view` field to the three session picker `Turn`
test fixtures that populate full turn item lists.

## Root Cause

`#21063` added `Turn.items_view` to the app-server protocol type. The
later session picker merge added three test-only
`codex_app_server_protocol::Turn` literals without the new field, which
broke Bazel compilation on `main` with `E0063: missing field
items_view`.

## Validation

- `just fmt`
- `cargo test -p codex-tui resume_picker --no-fail-fast`
- `just argument-comment-lint`

I also ran `cargo test -p codex-tui`; it compiled and ran the suite, but
this local machine failed two pre-existing status permission-profile
tests because `/etc/codex/requirements.toml` disallows
`DangerFullAccess`.
2026-05-05 14:24:28 -07:00
322 changed files with 21155 additions and 15190 deletions

View File

@@ -183,5 +183,15 @@ common:ci-v8 --build_metadata=TAG_os=linux
common:ci-v8 --config=remote
common:ci-v8 --strategy=remote
# Source-built Bazel V8 artifacts use the in-process sandbox by default. This
# does not affect Cargo's default prebuilt rusty_v8 path.
common --@v8//:v8_enable_pointer_compression=True
common --@v8//:v8_enable_sandbox=True
# Keep currently published rusty_v8 release artifacts non-sandboxed until the
# artifact migration ships matching Rust feature selection for Cargo consumers.
common:v8-release-compat --@v8//:v8_enable_pointer_compression=False
common:v8-release-compat --@v8//:v8_enable_sandbox=False
# Optional per-user local overrides.
try-import %workspace%/user.bazelrc

View File

@@ -11,11 +11,11 @@
"path": "codex"
},
"linux-x86_64": {
"regex": "^codex-x86_64-unknown-linux-musl\\.zst$",
"regex": "^codex-x86_64-unknown-linux-musl-bundle\\.tar\\.zst$",
"path": "codex"
},
"linux-aarch64": {
"regex": "^codex-aarch64-unknown-linux-musl\\.zst$",
"regex": "^codex-aarch64-unknown-linux-musl-bundle\\.tar\\.zst$",
"path": "codex"
},
"windows-x86_64": {
@@ -84,6 +84,18 @@
}
}
},
"bwrap": {
"platforms": {
"linux-x86_64": {
"regex": "^bwrap-x86_64-unknown-linux-musl\\.zst$",
"path": "bwrap"
},
"linux-aarch64": {
"regex": "^bwrap-aarch64-unknown-linux-musl\\.zst$",
"path": "bwrap"
}
}
},
"codex-command-runner": {
"platforms": {
"windows-x86_64": {

View File

@@ -63,8 +63,10 @@ def bazel_output_files(
platform: str,
labels: list[str],
compilation_mode: str = "fastbuild",
bazel_configs: list[str] | None = None,
) -> list[Path]:
expression = "set(" + " ".join(labels) + ")"
bazel_configs = bazel_configs or []
result = subprocess.run(
[
"bazel",
@@ -72,6 +74,7 @@ def bazel_output_files(
"-c",
compilation_mode,
f"--platforms=@llvm//platforms:{platform}",
*[f"--config={config}" for config in bazel_configs],
"--output=files",
expression,
],
@@ -87,7 +90,9 @@ def bazel_build(
platform: str,
labels: list[str],
compilation_mode: str = "fastbuild",
bazel_configs: list[str] | None = None,
) -> None:
bazel_configs = bazel_configs or []
subprocess.run(
[
"bazel",
@@ -95,6 +100,7 @@ def bazel_build(
"-c",
compilation_mode,
f"--platforms=@llvm//platforms:{platform}",
*[f"--config={config}" for config in bazel_configs],
*labels,
],
cwd=ROOT,
@@ -106,13 +112,14 @@ def ensure_bazel_output_files(
platform: str,
labels: list[str],
compilation_mode: str = "fastbuild",
bazel_configs: list[str] | None = None,
) -> list[Path]:
outputs = bazel_output_files(platform, labels, compilation_mode)
outputs = bazel_output_files(platform, labels, compilation_mode, bazel_configs)
if all(path.exists() for path in outputs):
return outputs
bazel_build(platform, labels, compilation_mode)
outputs = bazel_output_files(platform, labels, compilation_mode)
bazel_build(platform, labels, compilation_mode, bazel_configs)
outputs = bazel_output_files(platform, labels, compilation_mode, bazel_configs)
missing = [str(path) for path in outputs if not path.exists()]
if missing:
raise SystemExit(f"missing built outputs for {labels}: {missing}")
@@ -187,8 +194,9 @@ def single_bazel_output_file(
platform: str,
label: str,
compilation_mode: str = "fastbuild",
bazel_configs: list[str] | None = None,
) -> Path:
outputs = ensure_bazel_output_files(platform, [label], compilation_mode)
outputs = ensure_bazel_output_files(platform, [label], compilation_mode, bazel_configs)
if len(outputs) != 1:
raise SystemExit(f"expected exactly one output for {label}, found {outputs}")
return outputs[0]
@@ -198,11 +206,17 @@ def merged_musl_archive(
platform: str,
lib_path: Path,
compilation_mode: str = "fastbuild",
bazel_configs: list[str] | None = None,
) -> Path:
llvm_ar = single_bazel_output_file(platform, LLVM_AR_LABEL, compilation_mode)
llvm_ranlib = single_bazel_output_file(platform, LLVM_RANLIB_LABEL, compilation_mode)
llvm_ar = single_bazel_output_file(platform, LLVM_AR_LABEL, compilation_mode, bazel_configs)
llvm_ranlib = single_bazel_output_file(
platform,
LLVM_RANLIB_LABEL,
compilation_mode,
bazel_configs,
)
runtime_archives = [
single_bazel_output_file(platform, label, compilation_mode)
single_bazel_output_file(platform, label, compilation_mode, bazel_configs)
for label in MUSL_RUNTIME_ARCHIVE_LABELS
]
@@ -233,11 +247,13 @@ def stage_release_pair(
target: str,
output_dir: Path,
compilation_mode: str = "fastbuild",
bazel_configs: list[str] | None = None,
) -> None:
outputs = ensure_bazel_output_files(
platform,
[release_pair_label(target)],
compilation_mode,
bazel_configs,
)
try:
@@ -254,7 +270,7 @@ def stage_release_pair(
staged_library = output_dir / staged_archive_name(target, lib_path)
staged_binding = output_dir / f"src_binding_release_{target}.rs"
source_archive = (
merged_musl_archive(platform, lib_path, compilation_mode)
merged_musl_archive(platform, lib_path, compilation_mode, bazel_configs)
if is_musl_archive_target(target, lib_path)
else lib_path
)
@@ -293,6 +309,12 @@ def parse_args() -> argparse.Namespace:
stage_release_pair_parser.add_argument("--platform", required=True)
stage_release_pair_parser.add_argument("--target", required=True)
stage_release_pair_parser.add_argument("--output-dir", required=True)
stage_release_pair_parser.add_argument(
"--bazel-config",
action="append",
default=[],
dest="bazel_configs",
)
stage_release_pair_parser.add_argument(
"--compilation-mode",
default="fastbuild",
@@ -330,6 +352,7 @@ def main() -> int:
target=args.target,
output_dir=Path(args.output_dir),
compilation_mode=args.compilation_mode,
bazel_configs=args.bazel_configs,
)
return 0
if args.command == "resolved-v8-crate-version":

View File

@@ -25,7 +25,10 @@ TOP_LEVEL_NAME_EXCEPTIONS = {
UTILITY_NAME_EXCEPTIONS = {
"path-utils": "codex-utils-path",
}
MANIFEST_FEATURE_EXCEPTIONS = {}
MANIFEST_FEATURE_EXCEPTIONS = {
"codex-rs/code-mode/Cargo.toml": {"sandbox": ("v8/v8_enable_sandbox",)},
"codex-rs/v8-poc/Cargo.toml": {"sandbox": ("v8/v8_enable_sandbox",)},
}
OPTIONAL_DEPENDENCY_EXCEPTIONS = set()
INTERNAL_DEPENDENCY_FEATURE_EXCEPTIONS = {}

View File

@@ -371,6 +371,22 @@ jobs:
-- \
"${bazel_targets[@]}"
- name: Verify Bazel builds bwrap
if: runner.os == 'Linux'
env:
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
shell: bash
run: |
./.github/scripts/run-bazel-ci.sh \
--remote-download-toplevel \
--print-failed-action-summary \
-- \
build \
--build_metadata=COMMIT_SHA=${GITHUB_SHA} \
--build_metadata=TAG_job=verify-bwrap \
-- \
//codex-rs/bwrap:bwrap
- name: Upload Bazel execution logs
if: always() && !cancelled()
continue-on-error: true

View File

@@ -52,10 +52,12 @@ jobs:
CODEX_VERSION=0.125.0
WORKFLOW_URL="https://github.com/openai/codex/actions/runs/24901475298"
OUTPUT_DIR="${RUNNER_TEMP}"
# This reused workflow predates the standalone bwrap artifact.
python3 ./scripts/stage_npm_packages.py \
--release-version "$CODEX_VERSION" \
--workflow-url "$WORKFLOW_URL" \
--package codex \
--allow-missing-native-component bwrap \
--output-dir "$OUTPUT_DIR"
PACK_OUTPUT="${OUTPUT_DIR}/codex-npm-${CODEX_VERSION}.tgz"
echo "pack_output=$PACK_OUTPUT" >> "$GITHUB_OUTPUT"

View File

@@ -96,7 +96,7 @@ jobs:
target: x86_64-unknown-linux-musl
bundle: primary
artifact_name: x86_64-unknown-linux-musl
binaries: "codex codex-responses-api-proxy"
binaries: "codex codex-responses-api-proxy bwrap"
build_dmg: "false"
- runner: ubuntu-24.04
target: x86_64-unknown-linux-musl
@@ -108,7 +108,7 @@ jobs:
target: aarch64-unknown-linux-musl
bundle: primary
artifact_name: aarch64-unknown-linux-musl
binaries: "codex codex-responses-api-proxy"
binaries: "codex codex-responses-api-proxy bwrap"
build_dmg: "false"
- runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-musl
@@ -255,6 +255,24 @@ jobs:
with:
target: ${{ matrix.target }}
- if: ${{ contains(matrix.target, 'linux') && matrix.bundle == 'primary' }}
name: Build bwrap and export digest
shell: bash
run: |
set -euo pipefail
target="${{ matrix.target }}"
cargo build --target "$target" --release --timings --bin bwrap
bwrap_path="target/${target}/release/bwrap"
if [[ ! -f "$bwrap_path" ]]; then
echo "bwrap binary ${bwrap_path} not found"
exit 1
fi
digest="$(sha256sum "$bwrap_path" | awk '{print $1}')"
echo "CODEX_BWRAP_SHA256=${digest}" >> "$GITHUB_ENV"
echo "Built bwrap ${bwrap_path} with sha256:${digest}"
- name: Cargo build
shell: bash
run: |
@@ -361,6 +379,17 @@ jobs:
fi
done
if [[ "${{ matrix.target }}" == *linux* && "${{ matrix.bundle }}" == "primary" ]]; then
bundle_root="${RUNNER_TEMP}/codex-${{ matrix.target }}-bundle"
rm -rf "$bundle_root"
mkdir -p "$bundle_root/codex-resources"
cp "$dest/codex-${{ matrix.target }}" "$bundle_root/codex"
cp "$dest/bwrap-${{ matrix.target }}" "$bundle_root/codex-resources/bwrap"
chmod 0755 "$bundle_root/codex" "$bundle_root/codex-resources/bwrap"
tar -C "$bundle_root" -cf - codex codex-resources/bwrap |
zstd -T0 -19 -o "$dest/codex-${{ matrix.target }}-bundle.tar.zst"
fi
if [[ "${{ matrix.build_dmg }}" == "true" ]]; then
cp target/${{ matrix.target }}/release/codex-${{ matrix.target }}.dmg "$dest/codex-${{ matrix.target }}.dmg"
fi
@@ -384,7 +413,7 @@ jobs:
base="$(basename "$f")"
# Skip files that are already archives (shouldn't happen, but be
# safe).
if [[ "$base" == *.tar.gz || "$base" == *.zip || "$base" == *.dmg ]]; then
if [[ "$base" == *.tar.gz || "$base" == *.tar.zst || "$base" == *.zip || "$base" == *.dmg ]]; then
continue
fi
@@ -404,8 +433,8 @@ jobs:
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: ${{ matrix.artifact_name }}
# Upload the per-binary .zst files as well as the new .tar.gz
# equivalents we generated in the previous step.
# Upload the per-binary .zst files, .tar.gz equivalents, and any
# prebuilt archives staged above.
path: |
codex-rs/dist/${{ matrix.target }}/*

View File

@@ -1,20 +1,12 @@
name: rusty-v8-release
on:
workflow_dispatch:
inputs:
release_tag:
description: Optional release tag. Defaults to rusty-v8-v<resolved_v8_version>.
required: false
type: string
publish:
description: Publish the staged musl artifacts to a GitHub release.
required: false
default: true
type: boolean
push:
tags:
- "rusty-v8-v*.*.*"
concurrency:
group: ${{ github.workflow }}::${{ inputs.release_tag || github.run_id }}
group: ${{ github.workflow }}::${{ github.ref_name }}
cancel-in-progress: false
jobs:
@@ -43,15 +35,17 @@ jobs:
- name: Resolve release tag
id: release_tag
env:
RELEASE_TAG_INPUT: ${{ inputs.release_tag }}
GITHUB_REF_NAME: ${{ github.ref_name }}
V8_VERSION: ${{ steps.v8_version.outputs.version }}
shell: bash
run: |
set -euo pipefail
release_tag="${RELEASE_TAG_INPUT}"
if [[ -z "${release_tag}" ]]; then
release_tag="rusty-v8-v${V8_VERSION}"
expected_release_tag="rusty-v8-v${V8_VERSION}"
release_tag="${GITHUB_REF_NAME}"
if [[ "${release_tag}" != "${expected_release_tag}" ]]; then
echo "Tag ${release_tag} does not match resolved v8 crate version ${V8_VERSION}." >&2
exit 1
fi
echo "release_tag=${release_tag}" >> "$GITHUB_OUTPUT"
@@ -111,6 +105,7 @@ jobs:
-c
opt
"--platforms=@llvm//platforms:${PLATFORM}"
--config=v8-release-compat
"${pair_target}"
"${extra_targets[@]}"
--build_metadata=COMMIT_SHA=$(git rev-parse HEAD)
@@ -134,6 +129,7 @@ jobs:
--platform "${PLATFORM}" \
--target "${TARGET}" \
--compilation-mode opt \
--bazel-config v8-release-compat \
--output-dir "dist/${TARGET}"
- name: Upload staged musl artifacts
@@ -143,7 +139,6 @@ jobs:
path: dist/${{ matrix.target }}/*
publish-release:
if: ${{ inputs.publish }}
needs:
- metadata
- build
@@ -153,16 +148,6 @@ jobs:
actions: read
steps:
- name: Ensure publishing from default branch
if: ${{ github.ref_name != github.event.repository.default_branch }}
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
shell: bash
run: |
set -euo pipefail
echo "Publishing is only allowed from ${DEFAULT_BRANCH}; current ref is ${GITHUB_REF_NAME}." >&2
exit 1
- name: Ensure release tag is new
env:
GH_TOKEN: ${{ github.token }}

View File

@@ -105,6 +105,7 @@ jobs:
bazel_args=(
build
"--platforms=@llvm//platforms:${PLATFORM}"
--config=v8-release-compat
"${pair_target}"
"${extra_targets[@]}"
--build_metadata=COMMIT_SHA=$(git rev-parse HEAD)
@@ -127,6 +128,7 @@ jobs:
python3 .github/scripts/rusty_v8_bazel.py stage-release-pair \
--platform "${PLATFORM}" \
--target "${TARGET}" \
--bazel-config v8-release-compat \
--output-dir "dist/${TARGET}"
- name: Upload staged musl artifacts

View File

@@ -327,6 +327,18 @@ crate.annotation(
"RUSTY_V8_SRC_BINDING_PATH": "$(execpath @v8_targets//:rusty_v8_binding_for_target)",
},
crate = "v8",
# Keep the Rust feature aligned with the source-built Bazel artifacts.
# Windows MSVC still consumes upstream non-sandboxed prebuilts.
crate_features_select = {
"aarch64-apple-darwin": ["v8_enable_sandbox"],
"aarch64-pc-windows-gnullvm": ["v8_enable_sandbox"],
"aarch64-unknown-linux-gnu": ["v8_enable_sandbox"],
"aarch64-unknown-linux-musl": ["v8_enable_sandbox"],
"x86_64-apple-darwin": ["v8_enable_sandbox"],
"x86_64-pc-windows-gnullvm": ["v8_enable_sandbox"],
"x86_64-unknown-linux-gnu": ["v8_enable_sandbox"],
"x86_64-unknown-linux-musl": ["v8_enable_sandbox"],
},
gen_build_script = "on",
patch_args = ["-p1"],
patches = [

View File

@@ -69,8 +69,8 @@ PACKAGE_EXPANSIONS: dict[str, list[str]] = {
PACKAGE_NATIVE_COMPONENTS: dict[str, list[str]] = {
"codex": [],
"codex-linux-x64": ["codex", "rg"],
"codex-linux-arm64": ["codex", "rg"],
"codex-linux-x64": ["bwrap", "codex", "rg"],
"codex-linux-arm64": ["bwrap", "codex", "rg"],
"codex-darwin-x64": ["codex", "rg"],
"codex-darwin-arm64": ["codex", "rg"],
"codex-win32-x64": ["codex", "rg", "codex-windows-sandbox-setup", "codex-command-runner"],
@@ -87,6 +87,7 @@ PACKAGE_TARGET_FILTERS: dict[str, str] = {
PACKAGE_CHOICES = tuple(PACKAGE_NATIVE_COMPONENTS)
COMPONENT_DEST_DIR: dict[str, str] = {
"bwrap": "codex-resources",
"codex": "codex",
"codex-responses-api-proxy": "codex-responses-api-proxy",
"codex-windows-sandbox-setup": "codex",
@@ -137,6 +138,16 @@ def parse_args() -> argparse.Namespace:
type=Path,
help="Directory containing pre-installed native binaries to bundle (vendor root).",
)
parser.add_argument(
"--allow-missing-native-component",
dest="allow_missing_native_components",
action="append",
default=[],
help=(
"Native component that may be absent from --vendor-src. Intended for CI "
"compatibility with older artifact workflows; releases should not use this."
),
)
return parser.parse_args()
@@ -177,6 +188,7 @@ def main() -> int:
staging_dir,
native_components,
target_filter={target_filter} if target_filter else None,
allow_missing_components=set(args.allow_missing_native_components),
)
if release_version:
@@ -365,12 +377,14 @@ def copy_native_binaries(
staging_dir: Path,
components: list[str],
target_filter: set[str] | None = None,
allow_missing_components: set[str] | None = None,
) -> None:
vendor_src = vendor_src.resolve()
if not vendor_src.exists():
raise RuntimeError(f"Vendor source directory not found: {vendor_src}")
components_set = {component for component in components if component in COMPONENT_DEST_DIR}
allow_missing_components = allow_missing_components or set()
if not components_set:
return
@@ -399,6 +413,8 @@ def copy_native_binaries(
src_component_dir = target_dir / dest_dir_name
if not src_component_dir.exists():
if component in allow_missing_components:
continue
raise RuntimeError(
f"Missing native component '{component}' in vendor source: {src_component_dir}"
)

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Install Codex native binaries (Rust CLI plus ripgrep helpers)."""
"""Install Codex native binaries (Rust CLI, bwrap, and ripgrep helpers)."""
import argparse
from contextlib import contextmanager
@@ -42,8 +42,15 @@ class BinaryComponent:
WINDOWS_TARGETS = tuple(target for target in BINARY_TARGETS if "windows" in target)
LINUX_TARGETS = tuple(target for target in BINARY_TARGETS if "linux" in target)
BINARY_COMPONENTS = {
"bwrap": BinaryComponent(
artifact_prefix="bwrap",
dest_dir="codex-resources",
binary_basename="bwrap",
targets=LINUX_TARGETS,
),
"codex": BinaryComponent(
artifact_prefix="codex",
dest_dir="codex",
@@ -135,7 +142,7 @@ def parse_args() -> argparse.Namespace:
choices=tuple(list(BINARY_COMPONENTS) + ["rg"]),
help=(
"Limit installation to the specified components."
" May be repeated. Defaults to codex, codex-windows-sandbox-setup,"
" May be repeated. Defaults to bwrap, codex, codex-windows-sandbox-setup,"
" codex-command-runner, and rg."
),
)
@@ -159,6 +166,7 @@ def main() -> int:
vendor_dir.mkdir(parents=True, exist_ok=True)
components = args.components or [
"bwrap",
"codex",
"codex-windows-sandbox-setup",
"codex-command-runner",

16
codex-rs/Cargo.lock generated
View File

@@ -1944,6 +1944,7 @@ dependencies = [
"pretty_assertions",
"serde",
"serde_json",
"tempfile",
"tokio",
"tokio-tungstenite",
"toml 0.9.11+spec-1.1.0",
@@ -2125,6 +2126,15 @@ dependencies = [
"serde_with",
]
[[package]]
name = "codex-bwrap"
version = "0.0.0"
dependencies = [
"cc",
"libc",
"pkg-config",
]
[[package]]
name = "codex-chatgpt"
version = "0.0.0"
@@ -2411,6 +2421,7 @@ dependencies = [
"bm25",
"chrono",
"clap",
"codex-agent-graph-store",
"codex-analytics",
"codex-api",
"codex-app-server-protocol",
@@ -2700,6 +2711,7 @@ dependencies = [
"serde",
"serde_json",
"serial_test",
"sha2",
"tempfile",
"test-case",
"thiserror 2.0.18",
@@ -2708,6 +2720,7 @@ dependencies = [
"tokio-util",
"tracing",
"uuid",
"wiremock",
]
[[package]]
@@ -2902,7 +2915,6 @@ dependencies = [
name = "codex-linux-sandbox"
version = "0.0.0"
dependencies = [
"cc",
"clap",
"codex-core",
"codex-process-hardening",
@@ -2912,11 +2924,11 @@ dependencies = [
"globset",
"landlock",
"libc",
"pkg-config",
"pretty_assertions",
"seccompiler",
"serde",
"serde_json",
"sha2",
"tempfile",
"tokio",
"url",

View File

@@ -5,6 +5,7 @@ members = [
"agent-graph-store",
"agent-identity",
"backend-client",
"bwrap",
"ansi-escape",
"async-utils",
"app-server",

View File

@@ -79,6 +79,7 @@ use codex_app_server_protocol::Thread;
use codex_app_server_protocol::ThreadArchiveParams;
use codex_app_server_protocol::ThreadArchiveResponse;
use codex_app_server_protocol::ThreadResumeResponse;
use codex_app_server_protocol::ThreadSource as AppServerThreadSource;
use codex_app_server_protocol::ThreadStartResponse;
use codex_app_server_protocol::ThreadStatus as AppServerThreadStatus;
use codex_app_server_protocol::Turn;
@@ -107,6 +108,7 @@ use codex_protocol::protocol::HookSource;
use codex_protocol::protocol::SandboxPolicy;
use codex_protocol::protocol::SessionSource;
use codex_protocol::protocol::SubAgentSource;
use codex_protocol::protocol::ThreadSource;
use codex_protocol::protocol::TokenUsage;
use codex_utils_absolute_path::test_support::PathBufExt;
use codex_utils_absolute_path::test_support::test_path_buf;
@@ -118,14 +120,11 @@ use std::sync::Arc;
use std::sync::Mutex;
use tokio::sync::mpsc;
fn sample_thread(thread_id: &str, ephemeral: bool) -> Thread {
sample_thread_with_source(thread_id, ephemeral, AppServerSessionSource::Exec)
}
fn sample_thread_with_source(
fn sample_thread_with_metadata(
thread_id: &str,
ephemeral: bool,
source: AppServerSessionSource,
thread_source: Option<AppServerThreadSource>,
) -> Thread {
Thread {
id: thread_id.to_string(),
@@ -140,6 +139,7 @@ fn sample_thread_with_source(
cwd: test_path_buf("/tmp").abs(),
cli_version: "0.0.0".to_string(),
source,
thread_source,
agent_nickname: None,
agent_role: None,
git_info: None,
@@ -154,7 +154,13 @@ fn sample_thread_start_response(
model: &str,
) -> ClientResponsePayload {
ClientResponsePayload::ThreadStart(ThreadStartResponse {
thread: sample_thread(thread_id, ephemeral),
session_id: format!("session-{thread_id}"),
thread: sample_thread_with_metadata(
thread_id,
ephemeral,
AppServerSessionSource::Exec,
Some(AppServerThreadSource::User),
),
model: model.to_string(),
model_provider: "openai".to_string(),
service_tier: None,
@@ -198,6 +204,7 @@ fn sample_thread_resume_response(
ephemeral,
model,
AppServerSessionSource::Exec,
Some(AppServerThreadSource::User),
)
}
@@ -206,9 +213,11 @@ fn sample_thread_resume_response_with_source(
ephemeral: bool,
model: &str,
source: AppServerSessionSource,
thread_source: Option<AppServerThreadSource>,
) -> ClientResponsePayload {
ClientResponsePayload::ThreadResume(ThreadResumeResponse {
thread: sample_thread_with_source(thread_id, ephemeral, source),
session_id: format!("session-{thread_id}"),
thread: sample_thread_with_metadata(thread_id, ephemeral, source, thread_source),
model: model.to_string(),
model_provider: "openai".to_string(),
service_tier: None,
@@ -753,7 +762,7 @@ fn compaction_event_serializes_expected_shape() {
},
sample_app_server_client_metadata(),
sample_runtime_metadata(),
Some("user"),
Some(ThreadSource::User),
/*subagent_source*/ None,
/*parent_thread_id*/ None,
),
@@ -852,7 +861,7 @@ fn thread_initialized_event_serializes_expected_shape() {
},
model: "gpt-5".to_string(),
ephemeral: true,
thread_source: Some("user"),
thread_source: Some(ThreadSource::User),
initialization_mode: ThreadInitializationMode::New,
subagent_source: None,
parent_thread_id: None,
@@ -1196,6 +1205,7 @@ async fn compaction_event_ingests_custom_fact() {
agent_nickname: None,
agent_role: None,
}),
Some(AppServerThreadSource::Subagent),
)),
},
&mut events,
@@ -2116,7 +2126,7 @@ fn turn_event_serializes_expected_shape() {
runtime: sample_runtime_metadata(),
submission_type: None,
ephemeral: false,
thread_source: Some("user".to_string()),
thread_source: Some(ThreadSource::User),
initialization_mode: ThreadInitializationMode::New,
subagent_source: None,
parent_thread_id: None,

View File

@@ -87,6 +87,7 @@ fn sample_thread(thread_id: &str) -> Thread {
cwd: test_path_buf("/tmp").abs(),
cli_version: "0.0.0".to_string(),
source: AppServerSessionSource::Exec,
thread_source: None,
agent_nickname: None,
agent_role: None,
git_info: None,
@@ -101,6 +102,7 @@ fn sample_permission_profile() -> AppServerPermissionProfile {
fn sample_thread_start_response() -> ClientResponsePayload {
ClientResponsePayload::ThreadStart(ThreadStartResponse {
session_id: "session-1".to_string(),
thread: sample_thread("thread-1"),
model: "gpt-5".to_string(),
model_provider: "openai".to_string(),
@@ -118,6 +120,7 @@ fn sample_thread_start_response() -> ClientResponsePayload {
fn sample_thread_resume_response() -> ClientResponsePayload {
ClientResponsePayload::ThreadResume(ThreadResumeResponse {
session_id: "session-2".to_string(),
thread: sample_thread("thread-2"),
model: "gpt-5".to_string(),
model_provider: "openai".to_string(),
@@ -135,6 +138,7 @@ fn sample_thread_resume_response() -> ClientResponsePayload {
fn sample_thread_fork_response() -> ClientResponsePayload {
ClientResponsePayload::ThreadFork(ThreadForkResponse {
session_id: "session-3".to_string(),
thread: sample_thread("thread-3"),
model: "gpt-5".to_string(),
model_provider: "openai".to_string(),

View File

@@ -33,6 +33,7 @@ use codex_protocol::protocol::HookEventName;
use codex_protocol::protocol::HookRunStatus;
use codex_protocol::protocol::HookSource;
use codex_protocol::protocol::SubAgentSource;
use codex_protocol::protocol::ThreadSource;
use codex_protocol::protocol::TokenUsage;
use serde::Serialize;
@@ -126,7 +127,7 @@ pub(crate) struct ThreadInitializedEventParams {
pub(crate) runtime: CodexRuntimeMetadata,
pub(crate) model: String,
pub(crate) ephemeral: bool,
pub(crate) thread_source: Option<&'static str>,
pub(crate) thread_source: Option<ThreadSource>,
pub(crate) initialization_mode: ThreadInitializationMode,
pub(crate) subagent_source: Option<String>,
pub(crate) parent_thread_id: Option<String>,
@@ -647,7 +648,7 @@ pub(crate) struct CodexCompactionEventParams {
pub(crate) turn_id: String,
pub(crate) app_server_client: CodexAppServerClientMetadata,
pub(crate) runtime: CodexRuntimeMetadata,
pub(crate) thread_source: Option<&'static str>,
pub(crate) thread_source: Option<ThreadSource>,
pub(crate) subagent_source: Option<String>,
pub(crate) parent_thread_id: Option<String>,
pub(crate) trigger: CompactionTrigger,
@@ -680,7 +681,7 @@ pub(crate) struct CodexTurnEventParams {
pub(crate) app_server_client: CodexAppServerClientMetadata,
pub(crate) runtime: CodexRuntimeMetadata,
pub(crate) ephemeral: bool,
pub(crate) thread_source: Option<String>,
pub(crate) thread_source: Option<ThreadSource>,
pub(crate) initialization_mode: ThreadInitializationMode,
pub(crate) subagent_source: Option<String>,
pub(crate) parent_thread_id: Option<String>,
@@ -733,7 +734,7 @@ pub(crate) struct CodexTurnSteerEventParams {
pub(crate) accepted_turn_id: Option<String>,
pub(crate) app_server_client: CodexAppServerClientMetadata,
pub(crate) runtime: CodexRuntimeMetadata,
pub(crate) thread_source: Option<String>,
pub(crate) thread_source: Option<ThreadSource>,
pub(crate) subagent_source: Option<String>,
pub(crate) parent_thread_id: Option<String>,
pub(crate) num_input_images: usize,
@@ -836,7 +837,7 @@ pub(crate) fn codex_compaction_event_params(
input: CodexCompactionEvent,
app_server_client: CodexAppServerClientMetadata,
runtime: CodexRuntimeMetadata,
thread_source: Option<&'static str>,
thread_source: Option<ThreadSource>,
subagent_source: Option<String>,
parent_thread_id: Option<String>,
) -> CodexCompactionEventParams {
@@ -940,7 +941,7 @@ pub(crate) fn subagent_thread_started_event_request(
runtime: current_runtime_metadata(),
model: input.model,
ephemeral: input.ephemeral,
thread_source: Some("subagent"),
thread_source: Some(ThreadSource::Subagent),
initialization_mode: ThreadInitializationMode::New,
subagent_source: Some(subagent_source_name(&input.subagent_source)),
parent_thread_id: input

View File

@@ -64,6 +64,7 @@ use codex_protocol::config_types::ReasoningSummary;
use codex_protocol::models::PermissionProfile;
use codex_protocol::protocol::SessionSource;
use codex_protocol::protocol::SkillScope;
use codex_protocol::protocol::ThreadSource;
use codex_protocol::protocol::TokenUsage;
use sha1::Digest;
use std::collections::HashMap;
@@ -147,7 +148,7 @@ enum MissingAnalyticsContext {
#[derive(Clone)]
struct ThreadMetadataState {
thread_source: Option<&'static str>,
thread_source: Option<ThreadSource>,
initialization_mode: ThreadInitializationMode,
subagent_source: Option<String>,
parent_thread_id: Option<String>,
@@ -156,6 +157,7 @@ struct ThreadMetadataState {
impl ThreadMetadataState {
fn from_thread_metadata(
session_source: &SessionSource,
thread_source: Option<ThreadSource>,
initialization_mode: ThreadInitializationMode,
) -> Self {
let (subagent_source, parent_thread_id) = match session_source {
@@ -172,7 +174,7 @@ impl ThreadMetadataState {
| SessionSource::Unknown => (None, None),
};
Self {
thread_source: session_source.thread_source_name(),
thread_source,
initialization_mode,
subagent_source,
parent_thread_id,
@@ -348,7 +350,7 @@ impl AnalyticsReducer {
thread_state
.metadata
.get_or_insert_with(|| ThreadMetadataState {
thread_source: Some("subagent"),
thread_source: Some(ThreadSource::Subagent),
initialization_mode: ThreadInitializationMode::New,
subagent_source: Some(subagent_source_name(&input.subagent_source)),
parent_thread_id,
@@ -749,13 +751,16 @@ impl AnalyticsReducer {
initialization_mode: ThreadInitializationMode,
out: &mut Vec<TrackEventRequest>,
) {
let thread_source: SessionSource = thread.source.into();
let session_source: SessionSource = thread.source.into();
let thread_id = thread.id;
let Some(connection_state) = self.connections.get(&connection_id) else {
return;
};
let thread_metadata =
ThreadMetadataState::from_thread_metadata(&thread_source, initialization_mode);
let thread_metadata = ThreadMetadataState::from_thread_metadata(
&session_source,
thread.thread_source.map(Into::into),
initialization_mode,
);
self.threads.insert(
thread_id.clone(),
ThreadAnalyticsState {
@@ -857,7 +862,7 @@ impl AnalyticsReducer {
accepted_turn_id,
app_server_client: connection_state.app_server_client.clone(),
runtime: connection_state.runtime.clone(),
thread_source: thread_metadata.thread_source.map(str::to_string),
thread_source: thread_metadata.thread_source,
subagent_source: thread_metadata.subagent_source.clone(),
parent_thread_id: thread_metadata.parent_thread_id.clone(),
num_input_images: pending_request.num_input_images,
@@ -1023,7 +1028,7 @@ fn codex_turn_event_params(
runtime,
submission_type,
ephemeral,
thread_source: thread_metadata.thread_source.map(str::to_string),
thread_source: thread_metadata.thread_source,
initialization_mode: thread_metadata.initialization_mode,
subagent_source: thread_metadata.subagent_source.clone(),
parent_thread_id: thread_metadata.parent_thread_id.clone(),

View File

@@ -33,4 +33,5 @@ url = { workspace = true }
[dev-dependencies]
pretty_assertions = { workspace = true }
serde_json = { workspace = true }
tempfile = { workspace = true }
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }

View File

@@ -29,7 +29,6 @@ pub use codex_app_server::in_process::DEFAULT_IN_PROCESS_CHANNEL_CAPACITY;
pub use codex_app_server::in_process::InProcessServerEvent;
use codex_app_server::in_process::InProcessStartArgs;
use codex_app_server::in_process::LogDbLayer;
pub use codex_app_server::in_process::StateDbHandle;
use codex_app_server_protocol::ClientInfo;
use codex_app_server_protocol::ClientNotification;
use codex_app_server_protocol::ClientRequest;
@@ -47,6 +46,7 @@ use codex_config::LoaderOverrides;
use codex_config::NoopThreadConfigLoader;
use codex_config::RemoteThreadConfigLoader;
use codex_config::ThreadConfigLoader;
pub use codex_core::StateDbHandle;
use codex_core::config::Config;
pub use codex_exec_server::EnvironmentManager;
pub use codex_exec_server::EnvironmentManagerArgs;
@@ -954,9 +954,13 @@ mod tests {
use codex_app_server_protocol::ToolRequestUserInputParams;
use codex_app_server_protocol::ToolRequestUserInputQuestion;
use codex_core::config::ConfigBuilder;
use codex_core::init_state_db_from_config;
use futures::SinkExt;
use futures::StreamExt;
use pretty_assertions::assert_eq;
use std::ops::Deref;
use std::path::Path;
use tempfile::TempDir;
use tokio::net::TcpListener;
use tokio::time::Duration;
use tokio::time::timeout;
@@ -975,19 +979,59 @@ mod tests {
}
}
async fn build_test_config_for_codex_home(codex_home: &Path) -> Config {
match ConfigBuilder::default()
.codex_home(codex_home.to_path_buf())
.build()
.await
{
Ok(config) => config,
Err(_) => Config::load_default_with_cli_overrides_for_codex_home(
codex_home.to_path_buf(),
Vec::new(),
)
.await
.expect("default config should load"),
}
}
struct TestClient {
_codex_home: TempDir,
client: InProcessAppServerClient,
}
impl Deref for TestClient {
type Target = InProcessAppServerClient;
fn deref(&self) -> &Self::Target {
&self.client
}
}
impl TestClient {
async fn shutdown(self) -> IoResult<()> {
self.client.shutdown().await
}
}
async fn start_test_client_with_capacity(
session_source: SessionSource,
channel_capacity: usize,
) -> InProcessAppServerClient {
InProcessAppServerClient::start(InProcessClientStartArgs {
) -> TestClient {
let codex_home = TempDir::new().expect("temp dir");
let config = Arc::new(build_test_config_for_codex_home(codex_home.path()).await);
let state_db = init_state_db_from_config(config.as_ref())
.await
.expect("state db should initialize for in-process test");
let client = InProcessAppServerClient::start(InProcessClientStartArgs {
arg0_paths: Arg0DispatchPaths::default(),
config: Arc::new(build_test_config().await),
config,
cli_overrides: Vec::new(),
loader_overrides: LoaderOverrides::default(),
cloud_requirements: CloudRequirementsLoader::default(),
feedback: CodexFeedback::new(),
log_db: None,
state_db: None,
state_db: Some(state_db),
environment_manager: Arc::new(EnvironmentManager::default_for_tests()),
config_warnings: Vec::new(),
session_source,
@@ -999,10 +1043,15 @@ mod tests {
channel_capacity,
})
.await
.expect("in-process app-server client should start")
.expect("in-process app-server client should start");
TestClient {
_codex_home: codex_home,
client,
}
}
async fn start_test_client(session_source: SessionSource) -> InProcessAppServerClient {
async fn start_test_client(session_source: SessionSource) -> TestClient {
start_test_client_with_capacity(session_source, DEFAULT_IN_PROCESS_CHANNEL_CAPACITY).await
}

View File

@@ -2197,11 +2197,37 @@
],
"type": "object"
},
"PluginShareDiscoverability": {
"enum": [
"LISTED",
"UNLISTED",
"PRIVATE"
],
"type": "string"
},
"PluginShareListParams": {
"type": "object"
},
"PluginSharePrincipalType": {
"enum": [
"user",
"group",
"workspace"
],
"type": "string"
},
"PluginShareSaveParams": {
"properties": {
"discoverability": {
"anyOf": [
{
"$ref": "#/definitions/PluginShareDiscoverability"
},
{
"type": "null"
}
]
},
"pluginPath": {
"$ref": "#/definitions/AbsolutePathBuf"
},
@@ -2210,6 +2236,15 @@
"string",
"null"
]
},
"shareTargets": {
"items": {
"$ref": "#/definitions/PluginShareTarget"
},
"type": [
"array",
"null"
]
}
},
"required": [
@@ -2217,6 +2252,39 @@
],
"type": "object"
},
"PluginShareTarget": {
"properties": {
"principalId": {
"type": "string"
},
"principalType": {
"$ref": "#/definitions/PluginSharePrincipalType"
}
},
"required": [
"principalId",
"principalType"
],
"type": "object"
},
"PluginShareUpdateTargetsParams": {
"properties": {
"remotePluginId": {
"type": "string"
},
"shareTargets": {
"items": {
"$ref": "#/definitions/PluginShareTarget"
},
"type": "array"
}
},
"required": [
"remotePluginId",
"shareTargets"
],
"type": "object"
},
"PluginSkillReadParams": {
"properties": {
"remoteMarketplaceName": {
@@ -3558,6 +3626,17 @@
},
"threadId": {
"type": "string"
},
"threadSource": {
"anyOf": [
{
"$ref": "#/definitions/ThreadSource"
},
{
"type": "null"
}
],
"description": "Optional client-supplied analytics source classification for this forked thread."
}
},
"required": [
@@ -4032,6 +4111,14 @@
],
"type": "string"
},
"ThreadSource": {
"enum": [
"user",
"subagent",
"memory_consolidation"
],
"type": "string"
},
"ThreadSourceKind": {
"enum": [
"cli",
@@ -4165,6 +4252,17 @@
"type": "null"
}
]
},
"threadSource": {
"anyOf": [
{
"$ref": "#/definitions/ThreadSource"
},
{
"type": "null"
}
],
"description": "Optional client-supplied analytics source classification for this thread."
}
},
"type": "object"
@@ -5147,6 +5245,30 @@
"title": "Plugin/share/saveRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"plugin/share/updateTargets"
],
"title": "Plugin/share/updateTargetsRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/PluginShareUpdateTargetsParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Plugin/share/updateTargetsRequest",
"type": "object"
},
{
"properties": {
"id": {

View File

@@ -3088,6 +3088,17 @@
],
"description": "Current runtime status for the thread."
},
"threadSource": {
"anyOf": [
{
"$ref": "#/definitions/ThreadSource"
},
{
"type": "null"
}
],
"description": "Optional analytics source classification for this thread."
},
"turns": {
"description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.",
"items": {
@@ -4094,6 +4105,14 @@
],
"type": "object"
},
"ThreadSource": {
"enum": [
"user",
"subagent",
"memory_consolidation"
],
"type": "string"
},
"ThreadStartedNotification": {
"properties": {
"thread": {
@@ -6074,4 +6093,4 @@
}
],
"title": "ServerNotification"
}
}

View File

@@ -810,6 +810,30 @@
"title": "Plugin/share/saveRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/v2/RequestId"
},
"method": {
"enum": [
"plugin/share/updateTargets"
],
"title": "Plugin/share/updateTargetsRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/v2/PluginShareUpdateTargetsParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Plugin/share/updateTargetsRequest",
"type": "object"
},
{
"properties": {
"id": {
@@ -12479,6 +12503,14 @@
"title": "PluginShareDeleteResponse",
"type": "object"
},
"PluginShareDiscoverability": {
"enum": [
"LISTED",
"UNLISTED",
"PRIVATE"
],
"type": "string"
},
"PluginShareListItem": {
"properties": {
"localPluginPath": {
@@ -12525,9 +12557,46 @@
"title": "PluginShareListResponse",
"type": "object"
},
"PluginSharePrincipal": {
"properties": {
"name": {
"type": "string"
},
"principalId": {
"type": "string"
},
"principalType": {
"$ref": "#/definitions/v2/PluginSharePrincipalType"
}
},
"required": [
"name",
"principalId",
"principalType"
],
"type": "object"
},
"PluginSharePrincipalType": {
"enum": [
"user",
"group",
"workspace"
],
"type": "string"
},
"PluginShareSaveParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"discoverability": {
"anyOf": [
{
"$ref": "#/definitions/v2/PluginShareDiscoverability"
},
{
"type": "null"
}
]
},
"pluginPath": {
"$ref": "#/definitions/v2/AbsolutePathBuf"
},
@@ -12536,6 +12605,15 @@
"string",
"null"
]
},
"shareTargets": {
"items": {
"$ref": "#/definitions/v2/PluginShareTarget"
},
"type": [
"array",
"null"
]
}
},
"required": [
@@ -12561,6 +12639,57 @@
"title": "PluginShareSaveResponse",
"type": "object"
},
"PluginShareTarget": {
"properties": {
"principalId": {
"type": "string"
},
"principalType": {
"$ref": "#/definitions/v2/PluginSharePrincipalType"
}
},
"required": [
"principalId",
"principalType"
],
"type": "object"
},
"PluginShareUpdateTargetsParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"remotePluginId": {
"type": "string"
},
"shareTargets": {
"items": {
"$ref": "#/definitions/v2/PluginShareTarget"
},
"type": "array"
}
},
"required": [
"remotePluginId",
"shareTargets"
],
"title": "PluginShareUpdateTargetsParams",
"type": "object"
},
"PluginShareUpdateTargetsResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"principals": {
"items": {
"$ref": "#/definitions/v2/PluginSharePrincipal"
},
"type": "array"
}
},
"required": [
"principals"
],
"title": "PluginShareUpdateTargetsResponse",
"type": "object"
},
"PluginSkillReadParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
@@ -12710,6 +12839,13 @@
}
]
},
"keywords": {
"default": [],
"items": {
"type": "string"
},
"type": "array"
},
"name": {
"type": "string"
},
@@ -15225,6 +15361,17 @@
],
"description": "Current runtime status for the thread."
},
"threadSource": {
"anyOf": [
{
"$ref": "#/definitions/v2/ThreadSource"
},
{
"type": "null"
}
],
"description": "Optional analytics source classification for this thread."
},
"turns": {
"description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.",
"items": {
@@ -15439,6 +15586,17 @@
},
"threadId": {
"type": "string"
},
"threadSource": {
"anyOf": [
{
"$ref": "#/definitions/v2/ThreadSource"
},
{
"type": "null"
}
],
"description": "Optional client-supplied analytics source classification for this forked thread."
}
},
"required": [
@@ -15506,6 +15664,11 @@
}
]
},
"sessionId": {
"default": "",
"description": "Session id shared by threads that belong to the same session tree.",
"type": "string"
},
"thread": {
"$ref": "#/definitions/v2/Thread"
}
@@ -17013,6 +17176,11 @@
}
]
},
"sessionId": {
"default": "",
"description": "Session id shared by threads that belong to the same session tree.",
"type": "string"
},
"thread": {
"$ref": "#/definitions/v2/Thread"
}
@@ -17119,6 +17287,14 @@
],
"type": "string"
},
"ThreadSource": {
"enum": [
"user",
"subagent",
"memory_consolidation"
],
"type": "string"
},
"ThreadSourceKind": {
"enum": [
"cli",
@@ -17253,6 +17429,17 @@
"type": "null"
}
]
},
"threadSource": {
"anyOf": [
{
"$ref": "#/definitions/v2/ThreadSource"
},
{
"type": "null"
}
],
"description": "Optional client-supplied analytics source classification for this thread."
}
},
"title": "ThreadStartParams",
@@ -17317,6 +17504,11 @@
}
]
},
"sessionId": {
"default": "",
"description": "Session id shared by threads that belong to the same session tree.",
"type": "string"
},
"thread": {
"$ref": "#/definitions/v2/Thread"
}
@@ -18553,4 +18745,4 @@
},
"title": "CodexAppServerProtocol",
"type": "object"
}
}

View File

@@ -1569,6 +1569,30 @@
"title": "Plugin/share/saveRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"plugin/share/updateTargets"
],
"title": "Plugin/share/updateTargetsRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/PluginShareUpdateTargetsParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Plugin/share/updateTargetsRequest",
"type": "object"
},
{
"properties": {
"id": {
@@ -9090,6 +9114,14 @@
"title": "PluginShareDeleteResponse",
"type": "object"
},
"PluginShareDiscoverability": {
"enum": [
"LISTED",
"UNLISTED",
"PRIVATE"
],
"type": "string"
},
"PluginShareListItem": {
"properties": {
"localPluginPath": {
@@ -9136,9 +9168,46 @@
"title": "PluginShareListResponse",
"type": "object"
},
"PluginSharePrincipal": {
"properties": {
"name": {
"type": "string"
},
"principalId": {
"type": "string"
},
"principalType": {
"$ref": "#/definitions/PluginSharePrincipalType"
}
},
"required": [
"name",
"principalId",
"principalType"
],
"type": "object"
},
"PluginSharePrincipalType": {
"enum": [
"user",
"group",
"workspace"
],
"type": "string"
},
"PluginShareSaveParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"discoverability": {
"anyOf": [
{
"$ref": "#/definitions/PluginShareDiscoverability"
},
{
"type": "null"
}
]
},
"pluginPath": {
"$ref": "#/definitions/AbsolutePathBuf"
},
@@ -9147,6 +9216,15 @@
"string",
"null"
]
},
"shareTargets": {
"items": {
"$ref": "#/definitions/PluginShareTarget"
},
"type": [
"array",
"null"
]
}
},
"required": [
@@ -9172,6 +9250,57 @@
"title": "PluginShareSaveResponse",
"type": "object"
},
"PluginShareTarget": {
"properties": {
"principalId": {
"type": "string"
},
"principalType": {
"$ref": "#/definitions/PluginSharePrincipalType"
}
},
"required": [
"principalId",
"principalType"
],
"type": "object"
},
"PluginShareUpdateTargetsParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"remotePluginId": {
"type": "string"
},
"shareTargets": {
"items": {
"$ref": "#/definitions/PluginShareTarget"
},
"type": "array"
}
},
"required": [
"remotePluginId",
"shareTargets"
],
"title": "PluginShareUpdateTargetsParams",
"type": "object"
},
"PluginShareUpdateTargetsResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"principals": {
"items": {
"$ref": "#/definitions/PluginSharePrincipal"
},
"type": "array"
}
},
"required": [
"principals"
],
"title": "PluginShareUpdateTargetsResponse",
"type": "object"
},
"PluginSkillReadParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
@@ -9321,6 +9450,13 @@
}
]
},
"keywords": {
"default": [],
"items": {
"type": "string"
},
"type": "array"
},
"name": {
"type": "string"
},
@@ -13111,6 +13247,17 @@
],
"description": "Current runtime status for the thread."
},
"threadSource": {
"anyOf": [
{
"$ref": "#/definitions/ThreadSource"
},
{
"type": "null"
}
],
"description": "Optional analytics source classification for this thread."
},
"turns": {
"description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.",
"items": {
@@ -13325,6 +13472,17 @@
},
"threadId": {
"type": "string"
},
"threadSource": {
"anyOf": [
{
"$ref": "#/definitions/ThreadSource"
},
{
"type": "null"
}
],
"description": "Optional client-supplied analytics source classification for this forked thread."
}
},
"required": [
@@ -13392,6 +13550,11 @@
}
]
},
"sessionId": {
"default": "",
"description": "Session id shared by threads that belong to the same session tree.",
"type": "string"
},
"thread": {
"$ref": "#/definitions/Thread"
}
@@ -14899,6 +15062,11 @@
}
]
},
"sessionId": {
"default": "",
"description": "Session id shared by threads that belong to the same session tree.",
"type": "string"
},
"thread": {
"$ref": "#/definitions/Thread"
}
@@ -15005,6 +15173,14 @@
],
"type": "string"
},
"ThreadSource": {
"enum": [
"user",
"subagent",
"memory_consolidation"
],
"type": "string"
},
"ThreadSourceKind": {
"enum": [
"cli",
@@ -15139,6 +15315,17 @@
"type": "null"
}
]
},
"threadSource": {
"anyOf": [
{
"$ref": "#/definitions/ThreadSource"
},
{
"type": "null"
}
],
"description": "Optional client-supplied analytics source classification for this thread."
}
},
"title": "ThreadStartParams",
@@ -15203,6 +15390,11 @@
}
]
},
"sessionId": {
"default": "",
"description": "Session id shared by threads that belong to the same session tree.",
"type": "string"
},
"thread": {
"$ref": "#/definitions/Thread"
}
@@ -16438,4 +16630,4 @@
},
"title": "CodexAppServerProtocolV2",
"type": "object"
}
}

View File

@@ -1393,4 +1393,4 @@
],
"title": "ItemCompletedNotification",
"type": "object"
}
}

View File

@@ -1393,4 +1393,4 @@
],
"title": "ItemStartedNotification",
"type": "object"
}
}

View File

@@ -347,6 +347,13 @@
}
]
},
"keywords": {
"default": [],
"items": {
"type": "string"
},
"type": "array"
},
"name": {
"type": "string"
},

View File

@@ -366,6 +366,13 @@
}
]
},
"keywords": {
"default": [],
"items": {
"type": "string"
},
"type": "array"
},
"name": {
"type": "string"
},

View File

@@ -307,6 +307,13 @@
}
]
},
"keywords": {
"default": [],
"items": {
"type": "string"
},
"type": "array"
},
"name": {
"type": "string"
},

View File

@@ -4,9 +4,50 @@
"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.",
"type": "string"
},
"PluginShareDiscoverability": {
"enum": [
"LISTED",
"UNLISTED",
"PRIVATE"
],
"type": "string"
},
"PluginSharePrincipalType": {
"enum": [
"user",
"group",
"workspace"
],
"type": "string"
},
"PluginShareTarget": {
"properties": {
"principalId": {
"type": "string"
},
"principalType": {
"$ref": "#/definitions/PluginSharePrincipalType"
}
},
"required": [
"principalId",
"principalType"
],
"type": "object"
}
},
"properties": {
"discoverability": {
"anyOf": [
{
"$ref": "#/definitions/PluginShareDiscoverability"
},
{
"type": "null"
}
]
},
"pluginPath": {
"$ref": "#/definitions/AbsolutePathBuf"
},
@@ -15,6 +56,15 @@
"string",
"null"
]
},
"shareTargets": {
"items": {
"$ref": "#/definitions/PluginShareTarget"
},
"type": [
"array",
"null"
]
}
},
"required": [

View File

@@ -0,0 +1,45 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"PluginSharePrincipalType": {
"enum": [
"user",
"group",
"workspace"
],
"type": "string"
},
"PluginShareTarget": {
"properties": {
"principalId": {
"type": "string"
},
"principalType": {
"$ref": "#/definitions/PluginSharePrincipalType"
}
},
"required": [
"principalId",
"principalType"
],
"type": "object"
}
},
"properties": {
"remotePluginId": {
"type": "string"
},
"shareTargets": {
"items": {
"$ref": "#/definitions/PluginShareTarget"
},
"type": "array"
}
},
"required": [
"remotePluginId",
"shareTargets"
],
"title": "PluginShareUpdateTargetsParams",
"type": "object"
}

View File

@@ -0,0 +1,45 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"PluginSharePrincipal": {
"properties": {
"name": {
"type": "string"
},
"principalId": {
"type": "string"
},
"principalType": {
"$ref": "#/definitions/PluginSharePrincipalType"
}
},
"required": [
"name",
"principalId",
"principalType"
],
"type": "object"
},
"PluginSharePrincipalType": {
"enum": [
"user",
"group",
"workspace"
],
"type": "string"
}
},
"properties": {
"principals": {
"items": {
"$ref": "#/definitions/PluginSharePrincipal"
},
"type": "array"
}
},
"required": [
"principals"
],
"title": "PluginShareUpdateTargetsResponse",
"type": "object"
}

View File

@@ -137,6 +137,14 @@
"flex"
],
"type": "string"
},
"ThreadSource": {
"enum": [
"user",
"subagent",
"memory_consolidation"
],
"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.",
@@ -232,6 +240,17 @@
},
"threadId": {
"type": "string"
},
"threadSource": {
"anyOf": [
{
"$ref": "#/definitions/ThreadSource"
},
{
"type": "null"
}
],
"description": "Optional client-supplied analytics source classification for this forked thread."
}
},
"required": [

View File

@@ -1419,6 +1419,17 @@
],
"description": "Current runtime status for the thread."
},
"threadSource": {
"anyOf": [
{
"$ref": "#/definitions/ThreadSource"
},
{
"type": "null"
}
],
"description": "Optional analytics source classification for this thread."
},
"turns": {
"description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.",
"items": {
@@ -2117,6 +2128,14 @@
}
]
},
"ThreadSource": {
"enum": [
"user",
"subagent",
"memory_consolidation"
],
"type": "string"
},
"ThreadStatus": {
"oneOf": [
{
@@ -2600,6 +2619,11 @@
}
]
},
"sessionId": {
"default": "",
"description": "Session id shared by threads that belong to the same session tree.",
"type": "string"
},
"thread": {
"$ref": "#/definitions/Thread"
}

View File

@@ -869,6 +869,17 @@
],
"description": "Current runtime status for the thread."
},
"threadSource": {
"anyOf": [
{
"$ref": "#/definitions/ThreadSource"
},
{
"type": "null"
}
],
"description": "Optional analytics source classification for this thread."
},
"turns": {
"description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.",
"items": {
@@ -1567,6 +1578,14 @@
}
]
},
"ThreadSource": {
"enum": [
"user",
"subagent",
"memory_consolidation"
],
"type": "string"
},
"ThreadStatus": {
"oneOf": [
{

View File

@@ -869,6 +869,17 @@
],
"description": "Current runtime status for the thread."
},
"threadSource": {
"anyOf": [
{
"$ref": "#/definitions/ThreadSource"
},
{
"type": "null"
}
],
"description": "Optional analytics source classification for this thread."
},
"turns": {
"description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.",
"items": {
@@ -1567,6 +1578,14 @@
}
]
},
"ThreadSource": {
"enum": [
"user",
"subagent",
"memory_consolidation"
],
"type": "string"
},
"ThreadStatus": {
"oneOf": [
{

View File

@@ -869,6 +869,17 @@
],
"description": "Current runtime status for the thread."
},
"threadSource": {
"anyOf": [
{
"$ref": "#/definitions/ThreadSource"
},
{
"type": "null"
}
],
"description": "Optional analytics source classification for this thread."
},
"turns": {
"description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.",
"items": {
@@ -1567,6 +1578,14 @@
}
]
},
"ThreadSource": {
"enum": [
"user",
"subagent",
"memory_consolidation"
],
"type": "string"
},
"ThreadStatus": {
"oneOf": [
{

View File

@@ -1419,6 +1419,17 @@
],
"description": "Current runtime status for the thread."
},
"threadSource": {
"anyOf": [
{
"$ref": "#/definitions/ThreadSource"
},
{
"type": "null"
}
],
"description": "Optional analytics source classification for this thread."
},
"turns": {
"description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.",
"items": {
@@ -2117,6 +2128,14 @@
}
]
},
"ThreadSource": {
"enum": [
"user",
"subagent",
"memory_consolidation"
],
"type": "string"
},
"ThreadStatus": {
"oneOf": [
{
@@ -2600,6 +2619,11 @@
}
]
},
"sessionId": {
"default": "",
"description": "Session id shared by threads that belong to the same session tree.",
"type": "string"
},
"thread": {
"$ref": "#/definitions/Thread"
}

View File

@@ -869,6 +869,17 @@
],
"description": "Current runtime status for the thread."
},
"threadSource": {
"anyOf": [
{
"$ref": "#/definitions/ThreadSource"
},
{
"type": "null"
}
],
"description": "Optional analytics source classification for this thread."
},
"turns": {
"description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.",
"items": {
@@ -1567,6 +1578,14 @@
}
]
},
"ThreadSource": {
"enum": [
"user",
"subagent",
"memory_consolidation"
],
"type": "string"
},
"ThreadStatus": {
"oneOf": [
{

View File

@@ -172,6 +172,14 @@
],
"type": "string"
},
"ThreadSource": {
"enum": [
"user",
"subagent",
"memory_consolidation"
],
"type": "string"
},
"ThreadStartSource": {
"enum": [
"startup",
@@ -312,6 +320,17 @@
"type": "null"
}
]
},
"threadSource": {
"anyOf": [
{
"$ref": "#/definitions/ThreadSource"
},
{
"type": "null"
}
],
"description": "Optional client-supplied analytics source classification for this thread."
}
},
"title": "ThreadStartParams",

View File

@@ -1419,6 +1419,17 @@
],
"description": "Current runtime status for the thread."
},
"threadSource": {
"anyOf": [
{
"$ref": "#/definitions/ThreadSource"
},
{
"type": "null"
}
],
"description": "Optional analytics source classification for this thread."
},
"turns": {
"description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.",
"items": {
@@ -2117,6 +2128,14 @@
}
]
},
"ThreadSource": {
"enum": [
"user",
"subagent",
"memory_consolidation"
],
"type": "string"
},
"ThreadStatus": {
"oneOf": [
{
@@ -2600,6 +2619,11 @@
}
]
},
"sessionId": {
"default": "",
"description": "Session id shared by threads that belong to the same session tree.",
"type": "string"
},
"thread": {
"$ref": "#/definitions/Thread"
}

View File

@@ -869,6 +869,17 @@
],
"description": "Current runtime status for the thread."
},
"threadSource": {
"anyOf": [
{
"$ref": "#/definitions/ThreadSource"
},
{
"type": "null"
}
],
"description": "Optional analytics source classification for this thread."
},
"turns": {
"description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.",
"items": {
@@ -1567,6 +1578,14 @@
}
]
},
"ThreadSource": {
"enum": [
"user",
"subagent",
"memory_consolidation"
],
"type": "string"
},
"ThreadStatus": {
"oneOf": [
{

View File

@@ -869,6 +869,17 @@
],
"description": "Current runtime status for the thread."
},
"threadSource": {
"anyOf": [
{
"$ref": "#/definitions/ThreadSource"
},
{
"type": "null"
}
],
"description": "Optional analytics source classification for this thread."
},
"turns": {
"description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.",
"items": {
@@ -1567,6 +1578,14 @@
}
]
},
"ThreadSource": {
"enum": [
"user",
"subagent",
"memory_consolidation"
],
"type": "string"
},
"ThreadStatus": {
"oneOf": [
{

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,5 @@
// 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 PluginShareDiscoverability = "LISTED" | "UNLISTED" | "PRIVATE";

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 { PluginSharePrincipalType } from "./PluginSharePrincipalType";
export type PluginSharePrincipal = { principalType: PluginSharePrincipalType, principalId: string, name: string, };

View File

@@ -0,0 +1,5 @@
// 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 PluginSharePrincipalType = "user" | "group" | "workspace";

View File

@@ -2,5 +2,7 @@
// 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";
import type { PluginShareDiscoverability } from "./PluginShareDiscoverability";
import type { PluginShareTarget } from "./PluginShareTarget";
export type PluginShareSaveParams = { pluginPath: AbsolutePathBuf, remotePluginId?: string | null, };
export type PluginShareSaveParams = { pluginPath: AbsolutePathBuf, remotePluginId?: string | null, discoverability?: PluginShareDiscoverability | null, shareTargets?: Array<PluginShareTarget> | null, };

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 { PluginSharePrincipalType } from "./PluginSharePrincipalType";
export type PluginShareTarget = { principalType: PluginSharePrincipalType, principalId: 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 { PluginShareTarget } from "./PluginShareTarget";
export type PluginShareUpdateTargetsParams = { remotePluginId: string, shareTargets: Array<PluginShareTarget>, };

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 { PluginSharePrincipal } from "./PluginSharePrincipal";
export type PluginShareUpdateTargetsResponse = { principals: Array<PluginSharePrincipal>, };

View File

@@ -11,4 +11,4 @@ export type PluginSummary = { id: string, name: string, source: PluginSource, in
/**
* Availability state for installing and using the plugin.
*/
availability: PluginAvailability, interface: PluginInterface | null, };
availability: PluginAvailability, interface: PluginInterface | null, keywords: Array<string>, };

View File

@@ -4,6 +4,7 @@
import type { AbsolutePathBuf } from "../AbsolutePathBuf";
import type { GitInfo } from "./GitInfo";
import type { SessionSource } from "./SessionSource";
import type { ThreadSource } from "./ThreadSource";
import type { ThreadStatus } from "./ThreadStatus";
import type { Turn } from "./Turn";
@@ -52,6 +53,10 @@ cliVersion: string,
* Origin of the thread (CLI, VSCode, codex exec, codex app-server, etc.).
*/
source: SessionSource,
/**
* Optional analytics source classification for this thread.
*/
threadSource: ThreadSource | null,
/**
* Optional random unique nickname assigned to an AgentControl-spawned sub-agent.
*/

View File

@@ -6,6 +6,7 @@ import type { JsonValue } from "../serde_json/JsonValue";
import type { ApprovalsReviewer } from "./ApprovalsReviewer";
import type { AskForApproval } from "./AskForApproval";
import type { SandboxMode } from "./SandboxMode";
import type { ThreadSource } from "./ThreadSource";
/**
* There are two ways to fork a thread:
@@ -23,4 +24,7 @@ model?: string | null, modelProvider?: string | null, serviceTier?: ServiceTier
* Override where approval requests are routed for review on this thread
* and subsequent turns.
*/
approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, ephemeral?: boolean};
approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, ephemeral?: boolean, /**
* Optional client-supplied analytics source classification for this forked thread.
*/
threadSource?: ThreadSource | null};

View File

@@ -9,7 +9,10 @@ import type { AskForApproval } from "./AskForApproval";
import type { SandboxPolicy } from "./SandboxPolicy";
import type { Thread } from "./Thread";
export type ThreadForkResponse = {thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: AbsolutePathBuf, /**
export type ThreadForkResponse = {/**
* Session id shared by threads that belong to the same session tree.
*/
sessionId: string, thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: AbsolutePathBuf, /**
* Instruction source files currently loaded for this thread.
*/
instructionSources: Array<AbsolutePathBuf>, approvalPolicy: AskForApproval, /**

View File

@@ -9,7 +9,10 @@ import type { AskForApproval } from "./AskForApproval";
import type { SandboxPolicy } from "./SandboxPolicy";
import type { Thread } from "./Thread";
export type ThreadResumeResponse = {thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: AbsolutePathBuf, /**
export type ThreadResumeResponse = {/**
* Session id shared by threads that belong to the same session tree.
*/
sessionId: string, thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: AbsolutePathBuf, /**
* Instruction source files currently loaded for this thread.
*/
instructionSources: Array<AbsolutePathBuf>, approvalPolicy: AskForApproval, /**

View File

@@ -0,0 +1,5 @@
// 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 ThreadSource = "user" | "subagent" | "memory_consolidation";

View File

@@ -7,10 +7,14 @@ import type { JsonValue } from "../serde_json/JsonValue";
import type { ApprovalsReviewer } from "./ApprovalsReviewer";
import type { AskForApproval } from "./AskForApproval";
import type { SandboxMode } from "./SandboxMode";
import type { ThreadSource } from "./ThreadSource";
import type { ThreadStartSource } from "./ThreadStartSource";
export type ThreadStartParams = {model?: string | null, modelProvider?: string | null, serviceTier?: ServiceTier | null | null, cwd?: string | null, approvalPolicy?: AskForApproval | null, /**
* Override where approval requests are routed for review on this thread
* and subsequent turns.
*/
approvalsReviewer?: ApprovalsReviewer | 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, sessionStartSource?: ThreadStartSource | null};
approvalsReviewer?: ApprovalsReviewer | 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, sessionStartSource?: ThreadStartSource | null, /**
* Optional client-supplied analytics source classification for this thread.
*/
threadSource?: ThreadSource | null};

View File

@@ -9,7 +9,10 @@ import type { AskForApproval } from "./AskForApproval";
import type { SandboxPolicy } from "./SandboxPolicy";
import type { Thread } from "./Thread";
export type ThreadStartResponse = {thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: AbsolutePathBuf, /**
export type ThreadStartResponse = {/**
* Session id shared by threads that belong to the same session tree.
*/
sessionId: string, thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: AbsolutePathBuf, /**
* Instruction source files currently loaded for this thread.
*/
instructionSources: Array<AbsolutePathBuf>, approvalPolicy: AskForApproval, /**

View File

@@ -285,11 +285,17 @@ export type { PluginReadParams } from "./PluginReadParams";
export type { PluginReadResponse } from "./PluginReadResponse";
export type { PluginShareDeleteParams } from "./PluginShareDeleteParams";
export type { PluginShareDeleteResponse } from "./PluginShareDeleteResponse";
export type { PluginShareDiscoverability } from "./PluginShareDiscoverability";
export type { PluginShareListItem } from "./PluginShareListItem";
export type { PluginShareListParams } from "./PluginShareListParams";
export type { PluginShareListResponse } from "./PluginShareListResponse";
export type { PluginSharePrincipal } from "./PluginSharePrincipal";
export type { PluginSharePrincipalType } from "./PluginSharePrincipalType";
export type { PluginShareSaveParams } from "./PluginShareSaveParams";
export type { PluginShareSaveResponse } from "./PluginShareSaveResponse";
export type { PluginShareTarget } from "./PluginShareTarget";
export type { PluginShareUpdateTargetsParams } from "./PluginShareUpdateTargetsParams";
export type { PluginShareUpdateTargetsResponse } from "./PluginShareUpdateTargetsResponse";
export type { PluginSkillReadParams } from "./PluginSkillReadParams";
export type { PluginSkillReadResponse } from "./PluginSkillReadResponse";
export type { PluginSource } from "./PluginSource";
@@ -396,6 +402,7 @@ export type { ThreadSetNameResponse } from "./ThreadSetNameResponse";
export type { ThreadShellCommandParams } from "./ThreadShellCommandParams";
export type { ThreadShellCommandResponse } from "./ThreadShellCommandResponse";
export type { ThreadSortKey } from "./ThreadSortKey";
export type { ThreadSource } from "./ThreadSource";
export type { ThreadSourceKind } from "./ThreadSourceKind";
export type { ThreadStartParams } from "./ThreadStartParams";
export type { ThreadStartResponse } from "./ThreadStartResponse";

View File

@@ -77,6 +77,7 @@ macro_rules! experimental_type_entry {
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ClientRequestSerializationScope {
Global(&'static str),
GlobalSharedRead(&'static str),
Thread { thread_id: String },
ThreadPath { path: PathBuf },
CommandExecProcess { process_id: String },
@@ -93,6 +94,9 @@ macro_rules! serialization_scope_expr {
($actual_params:ident, global($key:literal)) => {
Some(ClientRequestSerializationScope::Global($key))
};
($actual_params:ident, global_shared_read($key:literal)) => {
Some(ClientRequestSerializationScope::GlobalSharedRead($key))
};
($actual_params:ident, thread_id($params:ident . $field:ident)) => {
Some(ClientRequestSerializationScope::Thread {
thread_id: $actual_params.$field.clone(),
@@ -585,7 +589,7 @@ client_request_definitions! {
},
SkillsList => "skills/list" {
params: v2::SkillsListParams,
serialization: global("config"),
serialization: global_shared_read("config"),
response: v2::SkillsListResponse,
},
HooksList => "hooks/list" {
@@ -610,7 +614,7 @@ client_request_definitions! {
},
PluginList => "plugin/list" {
params: v2::PluginListParams,
serialization: global("config"),
serialization: global_shared_read("config"),
response: v2::PluginListResponse,
},
PluginRead => "plugin/read" {
@@ -628,6 +632,11 @@ client_request_definitions! {
serialization: global("config"),
response: v2::PluginShareSaveResponse,
},
PluginShareUpdateTargets => "plugin/share/updateTargets" {
params: v2::PluginShareUpdateTargetsParams,
serialization: global("config"),
response: v2::PluginShareUpdateTargetsResponse,
},
PluginShareList => "plugin/share/list" {
params: v2::PluginShareListParams,
serialization: global("config"),
@@ -942,7 +951,7 @@ client_request_definitions! {
ConfigRead => "config/read" {
params: v2::ConfigReadParams,
serialization: global("config"),
serialization: global_shared_read("config"),
response: v2::ConfigReadResponse,
},
ExternalAgentConfigDetect => "externalAgentConfig/detect" {
@@ -1650,6 +1659,28 @@ mod tests {
Some(ClientRequestSerializationScope::Global("config"))
);
let skills_list = ClientRequest::SkillsList {
request_id: request_id(),
params: v2::SkillsListParams {
cwds: Vec::new(),
force_reload: false,
per_cwd_extra_user_roots: None,
},
};
assert_eq!(
skills_list.serialization_scope(),
Some(ClientRequestSerializationScope::GlobalSharedRead("config"))
);
let plugin_list = ClientRequest::PluginList {
request_id: request_id(),
params: v2::PluginListParams { cwds: None },
};
assert_eq!(
plugin_list.serialization_scope(),
Some(ClientRequestSerializationScope::GlobalSharedRead("config"))
);
let plugin_uninstall = ClientRequest::PluginUninstall {
request_id: request_id(),
params: v2::PluginUninstallParams {
@@ -1700,7 +1731,7 @@ mod tests {
};
assert_eq!(
config_read.serialization_scope(),
Some(ClientRequestSerializationScope::Global("config"))
Some(ClientRequestSerializationScope::GlobalSharedRead("config"))
);
let account_read = ClientRequest::GetAccount {
@@ -2171,6 +2202,7 @@ mod tests {
let response = ClientResponse::ThreadStart {
request_id: RequestId::Integer(7),
response: v2::ThreadStartResponse {
session_id: "67e55044-10b1-426f-9247-bb680e5fe0c7".to_string(),
thread: v2::Thread {
id: "67e55044-10b1-426f-9247-bb680e5fe0c8".to_string(),
forked_from_id: None,
@@ -2184,6 +2216,7 @@ mod tests {
cwd: cwd.clone(),
cli_version: "0.0.0".to_string(),
source: v2::SessionSource::Exec,
thread_source: None,
agent_nickname: None,
agent_role: None,
git_info: None,
@@ -2211,6 +2244,7 @@ mod tests {
"method": "thread/start",
"id": 7,
"response": {
"sessionId": "67e55044-10b1-426f-9247-bb680e5fe0c7",
"thread": {
"id": "67e55044-10b1-426f-9247-bb680e5fe0c8",
"forkedFromId": null,
@@ -2226,6 +2260,7 @@ mod tests {
"cwd": absolute_path_string("tmp"),
"cliVersion": "0.0.0",
"source": "exec",
"threadSource": null,
"agentNickname": null,
"agentRole": null,
"gitInfo": null,

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,383 @@
use crate::protocol::common::AuthMode;
use codex_experimental_api_macros::ExperimentalApi;
use codex_protocol::account::PlanType;
use codex_protocol::account::ProviderAccount;
use codex_protocol::protocol::CreditsSnapshot as CoreCreditsSnapshot;
use codex_protocol::protocol::RateLimitReachedType as CoreRateLimitReachedType;
use codex_protocol::protocol::RateLimitSnapshot as CoreRateLimitSnapshot;
use codex_protocol::protocol::RateLimitWindow as CoreRateLimitWindow;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::collections::HashMap;
use ts_rs::TS;
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "camelCase")]
#[ts(tag = "type")]
#[ts(export_to = "v2/")]
pub enum Account {
#[serde(rename = "apiKey", rename_all = "camelCase")]
#[ts(rename = "apiKey", rename_all = "camelCase")]
ApiKey {},
#[serde(rename = "chatgpt", rename_all = "camelCase")]
#[ts(rename = "chatgpt", rename_all = "camelCase")]
Chatgpt { email: String, plan_type: PlanType },
#[serde(rename = "amazonBedrock", rename_all = "camelCase")]
#[ts(rename = "amazonBedrock", rename_all = "camelCase")]
AmazonBedrock {},
}
impl From<ProviderAccount> for Account {
fn from(account: ProviderAccount) -> Self {
match account {
ProviderAccount::ApiKey => Self::ApiKey {},
ProviderAccount::Chatgpt { email, plan_type } => Self::Chatgpt { email, plan_type },
ProviderAccount::AmazonBedrock => Self::AmazonBedrock {},
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)]
#[serde(tag = "type")]
#[ts(tag = "type")]
#[ts(export_to = "v2/")]
pub enum LoginAccountParams {
#[serde(rename = "apiKey", rename_all = "camelCase")]
#[ts(rename = "apiKey", rename_all = "camelCase")]
ApiKey {
#[serde(rename = "apiKey")]
#[ts(rename = "apiKey")]
api_key: String,
},
#[serde(rename = "chatgpt", rename_all = "camelCase")]
#[ts(rename = "chatgpt", rename_all = "camelCase")]
Chatgpt {
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
codex_streamlined_login: bool,
},
#[serde(rename = "chatgptDeviceCode")]
#[ts(rename = "chatgptDeviceCode")]
ChatgptDeviceCode,
/// [UNSTABLE] FOR OPENAI INTERNAL USE ONLY - DO NOT USE.
/// The access token must contain the same scopes that Codex-managed ChatGPT auth tokens have.
#[experimental("account/login/start.chatgptAuthTokens")]
#[serde(rename = "chatgptAuthTokens", rename_all = "camelCase")]
#[ts(rename = "chatgptAuthTokens", rename_all = "camelCase")]
ChatgptAuthTokens {
/// Access token (JWT) supplied by the client.
/// This token is used for backend API requests and email extraction.
access_token: String,
/// Workspace/account identifier supplied by the client.
chatgpt_account_id: String,
/// Optional plan type supplied by the client.
///
/// When `null`, Codex attempts to derive the plan type from access-token
/// claims. If unavailable, the plan defaults to `unknown`.
#[ts(optional = nullable)]
chatgpt_plan_type: Option<String>,
},
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "camelCase")]
#[ts(tag = "type")]
#[ts(export_to = "v2/")]
pub enum LoginAccountResponse {
#[serde(rename = "apiKey", rename_all = "camelCase")]
#[ts(rename = "apiKey", rename_all = "camelCase")]
ApiKey {},
#[serde(rename = "chatgpt", rename_all = "camelCase")]
#[ts(rename = "chatgpt", rename_all = "camelCase")]
Chatgpt {
// Use plain String for identifiers to avoid TS/JSON Schema quirks around uuid-specific types.
// Convert to/from UUIDs at the application layer as needed.
login_id: String,
/// URL the client should open in a browser to initiate the OAuth flow.
auth_url: String,
},
#[serde(rename = "chatgptDeviceCode", rename_all = "camelCase")]
#[ts(rename = "chatgptDeviceCode", rename_all = "camelCase")]
ChatgptDeviceCode {
// Use plain String for identifiers to avoid TS/JSON Schema quirks around uuid-specific types.
// Convert to/from UUIDs at the application layer as needed.
login_id: String,
/// URL the client should open in a browser to complete device code authorization.
verification_url: String,
/// One-time code the user must enter after signing in.
user_code: String,
},
#[serde(rename = "chatgptAuthTokens", rename_all = "camelCase")]
#[ts(rename = "chatgptAuthTokens", rename_all = "camelCase")]
ChatgptAuthTokens {},
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct CancelLoginAccountParams {
pub login_id: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub enum CancelLoginAccountStatus {
Canceled,
NotFound,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct CancelLoginAccountResponse {
pub status: CancelLoginAccountStatus,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct LogoutAccountResponse {}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub enum ChatgptAuthTokensRefreshReason {
/// Codex attempted a backend request and received `401 Unauthorized`.
Unauthorized,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ChatgptAuthTokensRefreshParams {
pub reason: ChatgptAuthTokensRefreshReason,
/// Workspace/account identifier that Codex was previously using.
///
/// Clients that manage multiple accounts/workspaces can use this as a hint
/// to refresh the token for the correct workspace.
///
/// This may be `null` when the prior auth state did not include a workspace
/// identifier (`chatgpt_account_id`).
#[ts(optional = nullable)]
pub previous_account_id: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ChatgptAuthTokensRefreshResponse {
pub access_token: String,
pub chatgpt_account_id: String,
pub chatgpt_plan_type: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct GetAccountRateLimitsResponse {
/// Backward-compatible single-bucket view; mirrors the historical payload.
pub rate_limits: RateLimitSnapshot,
/// Multi-bucket view keyed by metered `limit_id` (for example, `codex`).
pub rate_limits_by_limit_id: Option<HashMap<String, RateLimitSnapshot>>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct SendAddCreditsNudgeEmailParams {
pub credit_type: AddCreditsNudgeCreditType,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export_to = "v2/", rename_all = "snake_case")]
pub enum AddCreditsNudgeCreditType {
Credits,
UsageLimit,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct SendAddCreditsNudgeEmailResponse {
pub status: AddCreditsNudgeEmailStatus,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export_to = "v2/", rename_all = "snake_case")]
pub enum AddCreditsNudgeEmailStatus {
Sent,
CooldownActive,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct GetAccountParams {
/// When `true`, requests a proactive token refresh before returning.
///
/// In managed auth mode this triggers the normal refresh-token flow. In
/// external auth mode this flag is ignored. Clients should refresh tokens
/// themselves and call `account/login/start` with `chatgptAuthTokens`.
#[serde(default)]
pub refresh_token: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct GetAccountResponse {
pub account: Option<Account>,
pub requires_openai_auth: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct AccountUpdatedNotification {
pub auth_mode: Option<AuthMode>,
pub plan_type: Option<PlanType>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct AccountRateLimitsUpdatedNotification {
pub rate_limits: RateLimitSnapshot,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct RateLimitSnapshot {
pub limit_id: Option<String>,
pub limit_name: Option<String>,
pub primary: Option<RateLimitWindow>,
pub secondary: Option<RateLimitWindow>,
pub credits: Option<CreditsSnapshot>,
pub plan_type: Option<PlanType>,
pub rate_limit_reached_type: Option<RateLimitReachedType>,
}
impl From<CoreRateLimitSnapshot> for RateLimitSnapshot {
fn from(value: CoreRateLimitSnapshot) -> Self {
Self {
limit_id: value.limit_id,
limit_name: value.limit_name,
primary: value.primary.map(RateLimitWindow::from),
secondary: value.secondary.map(RateLimitWindow::from),
credits: value.credits.map(CreditsSnapshot::from),
plan_type: value.plan_type,
rate_limit_reached_type: value
.rate_limit_reached_type
.map(RateLimitReachedType::from),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export_to = "v2/", rename_all = "snake_case")]
pub enum RateLimitReachedType {
RateLimitReached,
WorkspaceOwnerCreditsDepleted,
WorkspaceMemberCreditsDepleted,
WorkspaceOwnerUsageLimitReached,
WorkspaceMemberUsageLimitReached,
}
impl From<CoreRateLimitReachedType> for RateLimitReachedType {
fn from(value: CoreRateLimitReachedType) -> Self {
match value {
CoreRateLimitReachedType::RateLimitReached => Self::RateLimitReached,
CoreRateLimitReachedType::WorkspaceOwnerCreditsDepleted => {
Self::WorkspaceOwnerCreditsDepleted
}
CoreRateLimitReachedType::WorkspaceMemberCreditsDepleted => {
Self::WorkspaceMemberCreditsDepleted
}
CoreRateLimitReachedType::WorkspaceOwnerUsageLimitReached => {
Self::WorkspaceOwnerUsageLimitReached
}
CoreRateLimitReachedType::WorkspaceMemberUsageLimitReached => {
Self::WorkspaceMemberUsageLimitReached
}
}
}
}
impl From<RateLimitReachedType> for CoreRateLimitReachedType {
fn from(value: RateLimitReachedType) -> Self {
match value {
RateLimitReachedType::RateLimitReached => Self::RateLimitReached,
RateLimitReachedType::WorkspaceOwnerCreditsDepleted => {
Self::WorkspaceOwnerCreditsDepleted
}
RateLimitReachedType::WorkspaceMemberCreditsDepleted => {
Self::WorkspaceMemberCreditsDepleted
}
RateLimitReachedType::WorkspaceOwnerUsageLimitReached => {
Self::WorkspaceOwnerUsageLimitReached
}
RateLimitReachedType::WorkspaceMemberUsageLimitReached => {
Self::WorkspaceMemberUsageLimitReached
}
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct RateLimitWindow {
pub used_percent: i32,
#[ts(type = "number | null")]
pub window_duration_mins: Option<i64>,
#[ts(type = "number | null")]
pub resets_at: Option<i64>,
}
impl From<CoreRateLimitWindow> for RateLimitWindow {
fn from(value: CoreRateLimitWindow) -> Self {
Self {
used_percent: value.used_percent.round() as i32,
window_duration_mins: value.window_minutes,
resets_at: value.resets_at,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct CreditsSnapshot {
pub has_credits: bool,
pub unlimited: bool,
pub balance: Option<String>,
}
impl From<CoreCreditsSnapshot> for CreditsSnapshot {
fn from(value: CoreCreditsSnapshot) -> Self {
Self {
has_credits: value.has_credits,
unlimited: value.unlimited,
balance: value.balance,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct AccountLoginCompletedNotification {
// Use plain String for identifiers to avoid TS/JSON Schema quirks around uuid-specific types.
// Convert to/from UUIDs at the application layer as needed.
pub login_id: Option<String>,
pub success: bool,
pub error: Option<String>,
}

View File

@@ -0,0 +1,146 @@
use super::shared::default_enabled;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::collections::HashMap;
use ts_rs::TS;
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
/// EXPERIMENTAL - list available apps/connectors.
pub struct AppsListParams {
/// Opaque pagination cursor returned by a previous call.
#[ts(optional = nullable)]
pub cursor: Option<String>,
/// Optional page size; defaults to a reasonable server-side value.
#[ts(optional = nullable)]
pub limit: Option<u32>,
/// Optional thread id used to evaluate app feature gating from that thread's config.
#[ts(optional = nullable)]
pub thread_id: Option<String>,
/// When true, bypass app caches and fetch the latest data from sources.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub force_refetch: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
/// EXPERIMENTAL - app metadata returned by app-list APIs.
pub struct AppBranding {
pub category: Option<String>,
pub developer: Option<String>,
pub website: Option<String>,
pub privacy_policy: Option<String>,
pub terms_of_service: Option<String>,
pub is_discoverable_app: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct AppReview {
pub status: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct AppScreenshot {
pub url: Option<String>,
#[serde(alias = "file_id")]
pub file_id: Option<String>,
#[serde(alias = "user_prompt")]
pub user_prompt: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct AppMetadata {
pub review: Option<AppReview>,
pub categories: Option<Vec<String>>,
pub sub_categories: Option<Vec<String>>,
pub seo_description: Option<String>,
pub screenshots: Option<Vec<AppScreenshot>>,
pub developer: Option<String>,
pub version: Option<String>,
pub version_id: Option<String>,
pub version_notes: Option<String>,
pub first_party_type: Option<String>,
pub first_party_requires_install: Option<bool>,
pub show_in_composer_when_unlinked: Option<bool>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
/// EXPERIMENTAL - app metadata returned by app-list APIs.
pub struct AppInfo {
pub id: String,
pub name: String,
pub description: Option<String>,
pub logo_url: Option<String>,
pub logo_url_dark: Option<String>,
pub distribution_channel: Option<String>,
pub branding: Option<AppBranding>,
pub app_metadata: Option<AppMetadata>,
pub labels: Option<HashMap<String, String>>,
pub install_url: Option<String>,
#[serde(default)]
pub is_accessible: bool,
/// Whether this app is enabled in config.toml.
/// Example:
/// ```toml
/// [apps.bad_app]
/// enabled = false
/// ```
#[serde(default = "default_enabled")]
pub is_enabled: bool,
#[serde(default)]
pub plugin_display_names: Vec<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
/// EXPERIMENTAL - app metadata summary for plugin responses.
pub struct AppSummary {
pub id: String,
pub name: String,
pub description: Option<String>,
pub install_url: Option<String>,
pub needs_auth: bool,
}
impl From<AppInfo> for AppSummary {
fn from(value: AppInfo) -> Self {
Self {
id: value.id,
name: value.name,
description: value.description,
install_url: value.install_url,
needs_auth: false,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
/// EXPERIMENTAL - app list response.
pub struct AppsListResponse {
pub data: Vec<AppInfo>,
/// Opaque cursor to pass to the next call to continue after the last item.
/// If None, there are no more items to return.
pub next_cursor: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
/// EXPERIMENTAL - notification emitted when the app list changes.
pub struct AppListUpdatedNotification {
pub data: Vec<AppInfo>,
}

View File

@@ -0,0 +1,45 @@
use codex_protocol::config_types::CollaborationModeMask as CoreCollaborationModeMask;
use codex_protocol::config_types::ModeKind;
use codex_protocol::openai_models::ReasoningEffort;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use ts_rs::TS;
/// EXPERIMENTAL - list collaboration mode presets.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct CollaborationModeListParams {}
/// EXPERIMENTAL - collaboration mode preset metadata for clients.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct CollaborationModeMask {
pub name: String,
pub mode: Option<ModeKind>,
pub model: Option<String>,
#[serde(rename = "reasoning_effort")]
#[ts(rename = "reasoning_effort")]
pub reasoning_effort: Option<Option<ReasoningEffort>>,
}
impl From<CoreCollaborationModeMask> for CollaborationModeMask {
fn from(value: CoreCollaborationModeMask) -> Self {
Self {
name: value.name,
mode: value.mode,
model: value.model,
reasoning_effort: value.reasoning_effort,
}
}
}
/// EXPERIMENTAL - collaboration mode presets response.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct CollaborationModeListResponse {
pub data: Vec<CollaborationModeMask>,
}

View File

@@ -0,0 +1,214 @@
use super::PermissionProfile;
use super::SandboxPolicy;
use codex_experimental_api_macros::ExperimentalApi;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::collections::HashMap;
use std::path::PathBuf;
use ts_rs::TS;
/// PTY size in character cells for `command/exec` PTY sessions.
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct CommandExecTerminalSize {
/// Terminal height in character cells.
pub rows: u16,
/// Terminal width in character cells.
pub cols: u16,
}
/// Run a standalone command (argv vector) in the server sandbox without
/// creating a thread or turn.
///
/// The final `command/exec` response is deferred until the process exits and is
/// sent only after all `command/exec/outputDelta` notifications for that
/// connection have been emitted.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct CommandExecParams {
/// Command argv vector. Empty arrays are rejected.
pub command: Vec<String>,
/// Optional client-supplied, connection-scoped process id.
///
/// Required for `tty`, `streamStdin`, `streamStdoutStderr`, and follow-up
/// `command/exec/write`, `command/exec/resize`, and
/// `command/exec/terminate` calls. When omitted, buffered execution gets an
/// internal id that is not exposed to the client.
#[ts(optional = nullable)]
pub process_id: Option<String>,
/// Enable PTY mode.
///
/// This implies `streamStdin` and `streamStdoutStderr`.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub tty: bool,
/// Allow follow-up `command/exec/write` requests to write stdin bytes.
///
/// Requires a client-supplied `processId`.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub stream_stdin: bool,
/// Stream stdout/stderr via `command/exec/outputDelta` notifications.
///
/// Streamed bytes are not duplicated into the final response and require a
/// client-supplied `processId`.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub stream_stdout_stderr: bool,
/// Optional per-stream stdout/stderr capture cap in bytes.
///
/// When omitted, the server default applies. Cannot be combined with
/// `disableOutputCap`.
#[ts(type = "number | null")]
#[ts(optional = nullable)]
pub output_bytes_cap: Option<usize>,
/// Disable stdout/stderr capture truncation for this request.
///
/// Cannot be combined with `outputBytesCap`.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub disable_output_cap: bool,
/// Disable the timeout entirely for this request.
///
/// Cannot be combined with `timeoutMs`.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub disable_timeout: bool,
/// Optional timeout in milliseconds.
///
/// When omitted, the server default applies. Cannot be combined with
/// `disableTimeout`.
#[ts(type = "number | null")]
#[ts(optional = nullable)]
pub timeout_ms: Option<i64>,
/// Optional working directory. Defaults to the server cwd.
#[ts(optional = nullable)]
pub cwd: Option<PathBuf>,
/// Optional environment overrides merged into the server-computed
/// environment.
///
/// Matching names override inherited values. Set a key to `null` to unset
/// an inherited variable.
#[ts(optional = nullable)]
pub env: Option<HashMap<String, Option<String>>>,
/// Optional initial PTY size in character cells. Only valid when `tty` is
/// true.
#[ts(optional = nullable)]
pub size: Option<CommandExecTerminalSize>,
/// Optional sandbox policy for this command.
///
/// Uses the same shape as thread/turn execution sandbox configuration and
/// defaults to the user's configured policy when omitted. Cannot be
/// combined with `permissionProfile`.
#[ts(optional = nullable)]
pub sandbox_policy: Option<SandboxPolicy>,
/// Optional full permissions profile for this command.
///
/// Defaults to the user's configured permissions when omitted. Cannot be
/// combined with `sandboxPolicy`.
#[experimental("command/exec.permissionProfile")]
#[ts(optional = nullable)]
pub permission_profile: Option<PermissionProfile>,
}
/// Final buffered result for `command/exec`.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct CommandExecResponse {
/// Process exit code.
pub exit_code: i32,
/// Buffered stdout capture.
///
/// Empty when stdout was streamed via `command/exec/outputDelta`.
pub stdout: String,
/// Buffered stderr capture.
///
/// Empty when stderr was streamed via `command/exec/outputDelta`.
pub stderr: String,
}
/// Write stdin bytes to a running `command/exec` session, close stdin, or
/// both.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct CommandExecWriteParams {
/// Client-supplied, connection-scoped `processId` from the original
/// `command/exec` request.
pub process_id: String,
/// Optional base64-encoded stdin bytes to write.
#[ts(optional = nullable)]
pub delta_base64: Option<String>,
/// Close stdin after writing `deltaBase64`, if present.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub close_stdin: bool,
}
/// Empty success response for `command/exec/write`.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct CommandExecWriteResponse {}
/// Terminate a running `command/exec` session.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct CommandExecTerminateParams {
/// Client-supplied, connection-scoped `processId` from the original
/// `command/exec` request.
pub process_id: String,
}
/// Empty success response for `command/exec/terminate`.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct CommandExecTerminateResponse {}
/// Resize a running PTY-backed `command/exec` session.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct CommandExecResizeParams {
/// Client-supplied, connection-scoped `processId` from the original
/// `command/exec` request.
pub process_id: String,
/// New PTY size in character cells.
pub size: CommandExecTerminalSize,
}
/// Empty success response for `command/exec/resize`.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct CommandExecResizeResponse {}
/// Stream label for `command/exec/outputDelta` notifications.
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub enum CommandExecOutputStream {
/// stdout stream. PTY mode multiplexes terminal output here.
Stdout,
/// stderr stream.
Stderr,
}
/// Base64-encoded output chunk emitted for a streaming `command/exec` request.
///
/// These notifications are connection-scoped. If the originating connection
/// closes, the server terminates the process.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct CommandExecOutputDeltaNotification {
/// Client-supplied, connection-scoped `processId` from the original
/// `command/exec` request.
pub process_id: String,
/// Output stream for this chunk.
pub stream: CommandExecOutputStream,
/// Base64-encoded output bytes.
pub delta_base64: String,
/// `true` on the final streamed chunk for a stream when `outputBytesCap`
/// truncated later output on that stream.
pub cap_reached: bool,
}

View File

@@ -0,0 +1,703 @@
use super::ApprovalsReviewer;
use super::AskForApproval;
use super::SandboxMode;
use super::shared::default_enabled;
use codex_experimental_api_macros::ExperimentalApi;
use codex_protocol::config_types::ForcedLoginMethod;
use codex_protocol::config_types::ReasoningSummary;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::config_types::Verbosity;
use codex_protocol::config_types::WebSearchMode;
use codex_protocol::config_types::WebSearchToolConfig;
use codex_protocol::openai_models::ReasoningEffort;
use codex_utils_absolute_path::AbsolutePathBuf;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use serde_json::Value as JsonValue;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::path::PathBuf;
use ts_rs::TS;
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "camelCase")]
#[ts(tag = "type")]
#[ts(export_to = "v2/")]
pub enum ConfigLayerSource {
/// Managed preferences layer delivered by MDM (macOS only).
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
Mdm {
domain: String,
key: String,
},
/// Managed config layer from a file (usually `managed_config.toml`).
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
System {
/// This is the path to the system config.toml file, though it is not
/// guaranteed to exist.
file: AbsolutePathBuf,
},
/// User config layer from $CODEX_HOME/config.toml. This layer is special
/// in that it is expected to be:
/// - writable by the user
/// - generally outside the workspace directory
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
User {
/// This is the path to the user's config.toml file, though it is not
/// guaranteed to exist.
file: AbsolutePathBuf,
},
/// Path to a .codex/ folder within a project. There could be multiple of
/// these between `cwd` and the project/repo root.
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
Project {
dot_codex_folder: AbsolutePathBuf,
},
/// Session-layer overrides supplied via `-c`/`--config`.
SessionFlags,
/// `managed_config.toml` was designed to be a config that was loaded
/// as the last layer on top of everything else. This scheme did not quite
/// work out as intended, but we keep this variant as a "best effort" while
/// we phase out `managed_config.toml` in favor of `requirements.toml`.
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
LegacyManagedConfigTomlFromFile {
file: AbsolutePathBuf,
},
LegacyManagedConfigTomlFromMdm,
}
impl ConfigLayerSource {
/// A settings from a layer with a higher precedence will override a setting
/// from a layer with a lower precedence.
pub fn precedence(&self) -> i16 {
match self {
ConfigLayerSource::Mdm { .. } => 0,
ConfigLayerSource::System { .. } => 10,
ConfigLayerSource::User { .. } => 20,
ConfigLayerSource::Project { .. } => 25,
ConfigLayerSource::SessionFlags => 30,
ConfigLayerSource::LegacyManagedConfigTomlFromFile { .. } => 40,
ConfigLayerSource::LegacyManagedConfigTomlFromMdm => 50,
}
}
}
/// Compares [ConfigLayerSource] by precedence, so `A < B` means settings from
/// layer `A` will be overridden by settings from layer `B`.
impl PartialOrd for ConfigLayerSource {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.precedence().cmp(&other.precedence()))
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export_to = "v2/")]
pub struct SandboxWorkspaceWrite {
#[serde(default)]
pub writable_roots: Vec<PathBuf>,
#[serde(default)]
pub network_access: bool,
#[serde(default)]
pub exclude_tmpdir_env_var: bool,
#[serde(default)]
pub exclude_slash_tmp: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export_to = "v2/")]
pub struct ToolsV2 {
pub web_search: Option<WebSearchToolConfig>,
pub view_image: Option<bool>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)]
#[serde(rename_all = "snake_case")]
#[ts(export_to = "v2/")]
pub struct ProfileV2 {
pub model: Option<String>,
pub model_provider: Option<String>,
#[experimental(nested)]
pub approval_policy: Option<AskForApproval>,
/// [UNSTABLE] Optional profile-level override for where approval requests
/// are routed for review. If omitted, the enclosing config default is
/// used.
#[experimental("config/read.approvalsReviewer")]
pub approvals_reviewer: Option<ApprovalsReviewer>,
pub service_tier: Option<ServiceTier>,
pub model_reasoning_effort: Option<ReasoningEffort>,
pub model_reasoning_summary: Option<ReasoningSummary>,
pub model_verbosity: Option<Verbosity>,
pub web_search: Option<WebSearchMode>,
pub tools: Option<ToolsV2>,
pub chatgpt_base_url: Option<String>,
#[serde(default, flatten)]
pub additional: HashMap<String, JsonValue>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export_to = "v2/")]
pub struct AnalyticsConfig {
pub enabled: Option<bool>,
#[serde(default, flatten)]
pub additional: HashMap<String, JsonValue>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export_to = "v2/")]
pub enum AppToolApproval {
Auto,
Prompt,
Approve,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export_to = "v2/")]
pub struct AppsDefaultConfig {
#[serde(default = "default_enabled")]
pub enabled: bool,
#[serde(default = "default_enabled")]
pub destructive_enabled: bool,
#[serde(default = "default_enabled")]
pub open_world_enabled: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export_to = "v2/")]
pub struct AppToolConfig {
pub enabled: Option<bool>,
pub approval_mode: Option<AppToolApproval>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export_to = "v2/")]
pub struct AppToolsConfig {
#[serde(default, flatten)]
pub tools: HashMap<String, AppToolConfig>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export_to = "v2/")]
pub struct AppConfig {
#[serde(default = "default_enabled")]
pub enabled: bool,
pub destructive_enabled: Option<bool>,
pub open_world_enabled: Option<bool>,
pub default_tools_approval_mode: Option<AppToolApproval>,
pub default_tools_enabled: Option<bool>,
pub tools: Option<AppToolsConfig>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export_to = "v2/")]
pub struct AppsConfig {
#[serde(default, rename = "_default")]
pub default: Option<AppsDefaultConfig>,
#[serde(default, flatten)]
pub apps: HashMap<String, AppConfig>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)]
#[serde(rename_all = "snake_case")]
#[ts(export_to = "v2/")]
pub struct Config {
pub model: Option<String>,
pub review_model: Option<String>,
pub model_context_window: Option<i64>,
pub model_auto_compact_token_limit: Option<i64>,
pub model_provider: Option<String>,
#[experimental(nested)]
pub approval_policy: Option<AskForApproval>,
/// [UNSTABLE] Optional default for where approval requests are routed for
/// review.
#[experimental("config/read.approvalsReviewer")]
pub approvals_reviewer: Option<ApprovalsReviewer>,
pub sandbox_mode: Option<SandboxMode>,
pub sandbox_workspace_write: Option<SandboxWorkspaceWrite>,
pub forced_chatgpt_workspace_id: Option<String>,
pub forced_login_method: Option<ForcedLoginMethod>,
pub web_search: Option<WebSearchMode>,
pub tools: Option<ToolsV2>,
pub profile: Option<String>,
#[experimental(nested)]
#[serde(default)]
pub profiles: HashMap<String, ProfileV2>,
pub instructions: Option<String>,
pub developer_instructions: Option<String>,
pub compact_prompt: Option<String>,
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)]
pub apps: Option<AppsConfig>,
#[serde(default, flatten)]
pub additional: HashMap<String, JsonValue>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ConfigLayerMetadata {
pub name: ConfigLayerSource,
pub version: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ConfigLayer {
pub name: ConfigLayerSource,
pub version: String,
pub config: JsonValue,
#[serde(skip_serializing_if = "Option::is_none")]
pub disabled_reason: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub enum MergeStrategy {
Replace,
Upsert,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub enum WriteStatus {
Ok,
OkOverridden,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct OverriddenMetadata {
pub message: String,
pub overriding_layer: ConfigLayerMetadata,
pub effective_value: JsonValue,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ConfigWriteResponse {
pub status: WriteStatus,
pub version: String,
/// Canonical path to the config file that was written.
pub file_path: AbsolutePathBuf,
pub overridden_metadata: Option<OverriddenMetadata>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub enum ConfigWriteErrorCode {
ConfigLayerReadonly,
ConfigVersionConflict,
ConfigValidationError,
ConfigPathNotFound,
ConfigSchemaUnknownKey,
UserLayerNotFound,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ConfigReadParams {
#[serde(default)]
pub include_layers: bool,
/// Optional working directory to resolve project config layers. If specified,
/// return the effective config as seen from that directory (i.e., including any
/// project layers between `cwd` and the project/repo root).
#[ts(optional = nullable)]
pub cwd: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ConfigReadResponse {
#[experimental(nested)]
pub config: Config,
pub origins: HashMap<String, ConfigLayerMetadata>,
#[serde(skip_serializing_if = "Option::is_none")]
pub layers: Option<Vec<ConfigLayer>>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ConfigRequirements {
#[experimental(nested)]
pub allowed_approval_policies: Option<Vec<AskForApproval>>,
#[experimental("configRequirements/read.allowedApprovalsReviewers")]
pub allowed_approvals_reviewers: Option<Vec<ApprovalsReviewer>>,
pub allowed_sandbox_modes: Option<Vec<SandboxMode>>,
pub allowed_web_search_modes: Option<Vec<WebSearchMode>>,
pub feature_requirements: Option<BTreeMap<String, bool>>,
#[experimental("configRequirements/read.hooks")]
pub hooks: Option<ManagedHooksRequirements>,
pub enforce_residency: Option<ResidencyRequirement>,
#[experimental("configRequirements/read.network")]
pub network: Option<NetworkRequirements>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ManagedHooksRequirements {
pub managed_dir: Option<PathBuf>,
pub windows_managed_dir: Option<PathBuf>,
#[serde(rename = "PreToolUse")]
#[ts(rename = "PreToolUse")]
pub pre_tool_use: Vec<ConfiguredHookMatcherGroup>,
#[serde(rename = "PermissionRequest")]
#[ts(rename = "PermissionRequest")]
pub permission_request: Vec<ConfiguredHookMatcherGroup>,
#[serde(rename = "PostToolUse")]
#[ts(rename = "PostToolUse")]
pub post_tool_use: Vec<ConfiguredHookMatcherGroup>,
#[serde(rename = "SessionStart")]
#[ts(rename = "SessionStart")]
pub session_start: Vec<ConfiguredHookMatcherGroup>,
#[serde(rename = "UserPromptSubmit")]
#[ts(rename = "UserPromptSubmit")]
pub user_prompt_submit: Vec<ConfiguredHookMatcherGroup>,
#[serde(rename = "Stop")]
#[ts(rename = "Stop")]
pub stop: Vec<ConfiguredHookMatcherGroup>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ConfiguredHookMatcherGroup {
pub matcher: Option<String>,
pub hooks: Vec<ConfiguredHookHandler>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(tag = "type")]
#[ts(tag = "type", export_to = "v2/")]
pub enum ConfiguredHookHandler {
#[serde(rename = "command")]
#[ts(rename = "command")]
Command {
command: String,
#[serde(rename = "timeoutSec")]
#[ts(rename = "timeoutSec")]
timeout_sec: Option<u64>,
r#async: bool,
#[serde(rename = "statusMessage")]
#[ts(rename = "statusMessage")]
status_message: Option<String>,
},
#[serde(rename = "prompt")]
#[ts(rename = "prompt")]
Prompt {},
#[serde(rename = "agent")]
#[ts(rename = "agent")]
Agent {},
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct NetworkRequirements {
pub enabled: Option<bool>,
pub http_port: Option<u16>,
pub socks_port: Option<u16>,
pub allow_upstream_proxy: Option<bool>,
pub dangerously_allow_non_loopback_proxy: Option<bool>,
pub dangerously_allow_all_unix_sockets: Option<bool>,
/// Canonical network permission map for `experimental_network`.
pub domains: Option<BTreeMap<String, NetworkDomainPermission>>,
/// When true, only managed allowlist entries are respected while managed
/// network enforcement is active.
pub managed_allowed_domains_only: Option<bool>,
/// Legacy compatibility view derived from `domains`.
pub allowed_domains: Option<Vec<String>>,
/// Legacy compatibility view derived from `domains`.
pub denied_domains: Option<Vec<String>>,
/// Canonical unix socket permission map for `experimental_network`.
pub unix_sockets: Option<BTreeMap<String, NetworkUnixSocketPermission>>,
/// Legacy compatibility view derived from `unix_sockets`.
pub allow_unix_sockets: Option<Vec<String>>,
pub allow_local_binding: Option<bool>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "lowercase")]
#[ts(export_to = "v2/")]
pub enum NetworkDomainPermission {
Allow,
Deny,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "lowercase")]
#[ts(export_to = "v2/")]
pub enum NetworkUnixSocketPermission {
Allow,
None,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub enum ResidencyRequirement {
Us,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ConfigRequirementsReadResponse {
/// Null if no requirements are configured (e.g. no requirements.toml/MDM entries).
#[experimental(nested)]
pub requirements: Option<ConfigRequirements>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, JsonSchema, TS)]
#[ts(export_to = "v2/")]
pub enum ExternalAgentConfigMigrationItemType {
#[serde(rename = "AGENTS_MD")]
#[ts(rename = "AGENTS_MD")]
AgentsMd,
#[serde(rename = "CONFIG")]
#[ts(rename = "CONFIG")]
Config,
#[serde(rename = "SKILLS")]
#[ts(rename = "SKILLS")]
Skills,
#[serde(rename = "PLUGINS")]
#[ts(rename = "PLUGINS")]
Plugins,
#[serde(rename = "MCP_SERVER_CONFIG")]
#[ts(rename = "MCP_SERVER_CONFIG")]
McpServerConfig,
#[serde(rename = "SUBAGENTS")]
#[ts(rename = "SUBAGENTS")]
Subagents,
#[serde(rename = "HOOKS")]
#[ts(rename = "HOOKS")]
Hooks,
#[serde(rename = "COMMANDS")]
#[ts(rename = "COMMANDS")]
Commands,
#[serde(rename = "SESSIONS")]
#[ts(rename = "SESSIONS")]
Sessions,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginsMigration {
#[serde(rename = "marketplaceName")]
#[ts(rename = "marketplaceName")]
pub marketplace_name: String,
#[serde(rename = "pluginNames")]
#[ts(rename = "pluginNames")]
pub plugin_names: Vec<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct SessionMigration {
pub path: PathBuf,
pub cwd: PathBuf,
pub title: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct McpServerMigration {
pub name: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct HookMigration {
pub name: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct SubagentMigration {
pub name: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct CommandMigration {
pub name: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct MigrationDetails {
#[serde(default)]
pub plugins: Vec<PluginsMigration>,
#[serde(default)]
pub sessions: Vec<SessionMigration>,
#[serde(default)]
pub mcp_servers: Vec<McpServerMigration>,
#[serde(default)]
pub hooks: Vec<HookMigration>,
#[serde(default)]
pub subagents: Vec<SubagentMigration>,
#[serde(default)]
pub commands: Vec<CommandMigration>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ExternalAgentConfigMigrationItem {
pub item_type: ExternalAgentConfigMigrationItemType,
pub description: String,
/// Null or empty means home-scoped migration; non-empty means repo-scoped migration.
pub cwd: Option<PathBuf>,
pub details: Option<MigrationDetails>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ExternalAgentConfigDetectResponse {
pub items: Vec<ExternalAgentConfigMigrationItem>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ExternalAgentConfigDetectParams {
/// If true, include detection under the user's home (~/.claude, ~/.codex, etc.).
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub include_home: bool,
/// Zero or more working directories to include for repo-scoped detection.
#[ts(optional = nullable)]
pub cwds: Option<Vec<PathBuf>>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ExternalAgentConfigImportParams {
pub migration_items: Vec<ExternalAgentConfigMigrationItem>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ExternalAgentConfigImportResponse {}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ExternalAgentConfigImportCompletedNotification {}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ConfigValueWriteParams {
pub key_path: String,
pub value: JsonValue,
pub merge_strategy: MergeStrategy,
/// Path to the config file to write; defaults to the user's `config.toml` when omitted.
#[ts(optional = nullable)]
pub file_path: Option<String>,
#[ts(optional = nullable)]
pub expected_version: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ConfigBatchWriteParams {
pub edits: Vec<ConfigEdit>,
/// Path to the config file to write; defaults to the user's `config.toml` when omitted.
#[ts(optional = nullable)]
pub file_path: Option<String>,
#[ts(optional = nullable)]
pub expected_version: Option<String>,
/// When true, hot-reload the updated user config into all loaded threads after writing.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub reload_user_config: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ConfigEdit {
pub key_path: String,
pub value: JsonValue,
pub merge_strategy: MergeStrategy,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct TextPosition {
/// 1-based line number.
pub line: usize,
/// 1-based column number (in Unicode scalar values).
pub column: usize,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct TextRange {
pub start: TextPosition,
pub end: TextPosition,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ConfigWarningNotification {
/// Concise summary of the warning.
pub summary: String,
/// Optional extra guidance or error details.
pub details: Option<String>,
/// Optional path to the config file that triggered the warning.
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub path: Option<String>,
/// Optional range for the error location inside the config file.
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub range: Option<TextRange>,
}

View File

@@ -0,0 +1,181 @@
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use ts_rs::TS;
/// Device-key algorithm reported at enrollment and signing boundaries.
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "snake_case")]
#[ts(rename_all = "snake_case", export_to = "v2/")]
pub enum DeviceKeyAlgorithm {
EcdsaP256Sha256,
}
/// Platform protection class for a controller-local device key.
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "snake_case")]
#[ts(rename_all = "snake_case", export_to = "v2/")]
pub enum DeviceKeyProtectionClass {
HardwareSecureEnclave,
HardwareTpm,
OsProtectedNonextractable,
}
/// Protection policy for creating or loading a controller-local device key.
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "snake_case")]
#[ts(rename_all = "snake_case", export_to = "v2/")]
pub enum DeviceKeyProtectionPolicy {
HardwareOnly,
AllowOsProtectedNonextractable,
}
/// Create a controller-local device key with a random key id.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct DeviceKeyCreateParams {
/// Defaults to `hardware_only` when omitted.
#[ts(optional = nullable)]
pub protection_policy: Option<DeviceKeyProtectionPolicy>,
pub account_user_id: String,
pub client_id: String,
}
/// Device-key metadata and public key returned by create/public APIs.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct DeviceKeyCreateResponse {
pub key_id: String,
/// SubjectPublicKeyInfo DER encoded as base64.
pub public_key_spki_der_base64: String,
pub algorithm: DeviceKeyAlgorithm,
pub protection_class: DeviceKeyProtectionClass,
}
/// Fetch a controller-local device key public key by id.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct DeviceKeyPublicParams {
pub key_id: String,
}
/// Device-key public metadata returned by `device/key/public`.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct DeviceKeyPublicResponse {
pub key_id: String,
/// SubjectPublicKeyInfo DER encoded as base64.
pub public_key_spki_der_base64: String,
pub algorithm: DeviceKeyAlgorithm,
pub protection_class: DeviceKeyProtectionClass,
}
/// Current remote-control connection status and environment id exposed to clients.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct RemoteControlStatusChangedNotification {
pub status: RemoteControlConnectionStatus,
pub environment_id: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase", export_to = "v2/")]
pub enum RemoteControlConnectionStatus {
Disabled,
Connecting,
Connected,
Errored,
}
/// Audience for a remote-control client connection device-key proof.
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "snake_case")]
#[ts(rename_all = "snake_case", export_to = "v2/")]
pub enum RemoteControlClientConnectionAudience {
RemoteControlClientWebsocket,
}
/// Audience for a remote-control client enrollment device-key proof.
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "snake_case")]
#[ts(rename_all = "snake_case", export_to = "v2/")]
pub enum RemoteControlClientEnrollmentAudience {
RemoteControlClientEnrollment,
}
/// Structured payloads accepted by `device/key/sign`.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "camelCase")]
#[ts(tag = "type", export_to = "v2/")]
pub enum DeviceKeySignPayload {
/// Payload bound to one remote-control controller websocket `/client` connection challenge.
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
RemoteControlClientConnection {
nonce: String,
audience: RemoteControlClientConnectionAudience,
/// Backend-issued websocket session id that this proof authorizes.
session_id: String,
/// Origin of the backend endpoint that issued the challenge and will verify this proof.
target_origin: String,
/// Websocket route path that this proof authorizes.
target_path: String,
account_user_id: String,
client_id: String,
/// Remote-control token expiration as Unix seconds.
#[ts(type = "number")]
token_expires_at: i64,
/// SHA-256 of the controller-scoped remote-control token, encoded as unpadded base64url.
token_sha256_base64url: String,
/// Must contain exactly `remote_control_controller_websocket`.
scopes: Vec<String>,
},
/// Payload bound to a remote-control client `/client/enroll` ownership challenge.
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
RemoteControlClientEnrollment {
nonce: String,
audience: RemoteControlClientEnrollmentAudience,
/// Backend-issued enrollment challenge id that this proof authorizes.
challenge_id: String,
/// Origin of the backend endpoint that issued the challenge and will verify this proof.
target_origin: String,
/// HTTP route path that this proof authorizes.
target_path: String,
account_user_id: String,
client_id: String,
/// SHA-256 of the requested device identity operation, encoded as unpadded base64url.
device_identity_sha256_base64url: String,
/// Enrollment challenge expiration as Unix seconds.
#[ts(type = "number")]
challenge_expires_at: i64,
},
}
/// Sign an accepted structured payload with a controller-local device key.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct DeviceKeySignParams {
pub key_id: String,
pub payload: DeviceKeySignPayload,
}
/// ASN.1 DER signature returned by `device/key/sign`.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct DeviceKeySignResponse {
/// ECDSA signature DER encoded as base64.
pub signature_der_base64: String,
/// Exact bytes signed by the device key, encoded as base64. Verifiers must verify this byte
/// string directly and must not reserialize `payload`.
pub signed_payload_base64: String,
pub algorithm: DeviceKeyAlgorithm,
}

View File

@@ -0,0 +1,85 @@
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::collections::BTreeMap;
use ts_rs::TS;
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ExperimentalFeatureListParams {
/// Opaque pagination cursor returned by a previous call.
#[ts(optional = nullable)]
pub cursor: Option<String>,
/// Optional page size; defaults to a reasonable server-side value.
#[ts(optional = nullable)]
pub limit: Option<u32>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub enum ExperimentalFeatureStage {
/// Feature is available for user testing and feedback.
Beta,
/// Feature is still being built and not ready for broad use.
UnderDevelopment,
/// Feature is production-ready.
Stable,
/// Feature is deprecated and should be avoided.
Deprecated,
/// Feature flag is retained only for backwards compatibility.
Removed,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ExperimentalFeature {
/// Stable key used in config.toml and CLI flag toggles.
pub name: String,
/// Lifecycle stage of this feature flag.
pub stage: ExperimentalFeatureStage,
/// User-facing display name shown in the experimental features UI.
/// Null when this feature is not in beta.
pub display_name: Option<String>,
/// Short summary describing what the feature does.
/// Null when this feature is not in beta.
pub description: Option<String>,
/// Announcement copy shown to users when the feature is introduced.
/// Null when this feature is not in beta.
pub announcement: Option<String>,
/// Whether this feature is currently enabled in the loaded config.
pub enabled: bool,
/// Whether this feature is enabled by default.
pub default_enabled: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ExperimentalFeatureListResponse {
pub data: Vec<ExperimentalFeature>,
/// Opaque cursor to pass to the next call to continue after the last item.
/// If None, there are no more items to return.
pub next_cursor: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ExperimentalFeatureEnablementSetParams {
/// Process-wide runtime feature enablement keyed by canonical feature name.
///
/// Only named features are updated. Omitted features are left unchanged.
/// Send an empty map for a no-op.
pub enablement: BTreeMap<String, bool>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ExperimentalFeatureEnablementSetResponse {
/// Feature enablement entries updated by this request.
pub enablement: BTreeMap<String, bool>,
}

View File

@@ -0,0 +1,29 @@
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::collections::BTreeMap;
use std::path::PathBuf;
use ts_rs::TS;
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct FeedbackUploadParams {
pub classification: String,
#[ts(optional = nullable)]
pub reason: Option<String>,
#[ts(optional = nullable)]
pub thread_id: Option<String>,
pub include_logs: bool,
#[ts(optional = nullable)]
pub extra_log_files: Option<Vec<PathBuf>>,
#[ts(optional = nullable)]
pub tags: Option<BTreeMap<String, String>>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct FeedbackUploadResponse {
pub thread_id: String,
}

View File

@@ -0,0 +1,204 @@
use codex_utils_absolute_path::AbsolutePathBuf;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use ts_rs::TS;
/// Read a file from the host filesystem.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct FsReadFileParams {
/// Absolute path to read.
pub path: AbsolutePathBuf,
}
/// Base64-encoded file contents returned by `fs/readFile`.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct FsReadFileResponse {
/// File contents encoded as base64.
pub data_base64: String,
}
/// Write a file on the host filesystem.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct FsWriteFileParams {
/// Absolute path to write.
pub path: AbsolutePathBuf,
/// File contents encoded as base64.
pub data_base64: String,
}
/// Successful response for `fs/writeFile`.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct FsWriteFileResponse {}
/// Create a directory on the host filesystem.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct FsCreateDirectoryParams {
/// Absolute directory path to create.
pub path: AbsolutePathBuf,
/// Whether parent directories should also be created. Defaults to `true`.
#[ts(optional = nullable)]
pub recursive: Option<bool>,
}
/// Successful response for `fs/createDirectory`.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct FsCreateDirectoryResponse {}
/// Request metadata for an absolute path.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct FsGetMetadataParams {
/// Absolute path to inspect.
pub path: AbsolutePathBuf,
}
/// Metadata returned by `fs/getMetadata`.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct FsGetMetadataResponse {
/// Whether the path resolves to a directory.
pub is_directory: bool,
/// Whether the path resolves to a regular file.
pub is_file: bool,
/// Whether the path itself is a symbolic link.
pub is_symlink: bool,
/// File creation time in Unix milliseconds when available, otherwise `0`.
#[ts(type = "number")]
pub created_at_ms: i64,
/// File modification time in Unix milliseconds when available, otherwise `0`.
#[ts(type = "number")]
pub modified_at_ms: i64,
}
/// List direct child names for a directory.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct FsReadDirectoryParams {
/// Absolute directory path to read.
pub path: AbsolutePathBuf,
}
/// A directory entry returned by `fs/readDirectory`.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct FsReadDirectoryEntry {
/// Direct child entry name only, not an absolute or relative path.
pub file_name: String,
/// Whether this entry resolves to a directory.
pub is_directory: bool,
/// Whether this entry resolves to a regular file.
pub is_file: bool,
}
/// Directory entries returned by `fs/readDirectory`.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct FsReadDirectoryResponse {
/// Direct child entries in the requested directory.
pub entries: Vec<FsReadDirectoryEntry>,
}
/// Remove a file or directory tree from the host filesystem.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct FsRemoveParams {
/// Absolute path to remove.
pub path: AbsolutePathBuf,
/// Whether directory removal should recurse. Defaults to `true`.
#[ts(optional = nullable)]
pub recursive: Option<bool>,
/// Whether missing paths should be ignored. Defaults to `true`.
#[ts(optional = nullable)]
pub force: Option<bool>,
}
/// Successful response for `fs/remove`.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct FsRemoveResponse {}
/// Copy a file or directory tree on the host filesystem.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct FsCopyParams {
/// Absolute source path.
pub source_path: AbsolutePathBuf,
/// Absolute destination path.
pub destination_path: AbsolutePathBuf,
/// Required for directory copies; ignored for file copies.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub recursive: bool,
}
/// Successful response for `fs/copy`.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct FsCopyResponse {}
/// Start filesystem watch notifications for an absolute path.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct FsWatchParams {
/// Connection-scoped watch identifier used for `fs/unwatch` and `fs/changed`.
pub watch_id: String,
/// Absolute file or directory path to watch.
pub path: AbsolutePathBuf,
}
/// Successful response for `fs/watch`.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct FsWatchResponse {
/// Canonicalized path associated with the watch.
pub path: AbsolutePathBuf,
}
/// Stop filesystem watch notifications for a prior `fs/watch`.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct FsUnwatchParams {
/// Watch identifier previously provided to `fs/watch`.
pub watch_id: String,
}
/// Successful response for `fs/unwatch`.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct FsUnwatchResponse {}
/// Filesystem watch notification emitted for `fs/watch` subscribers.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct FsChangedNotification {
/// Watch identifier previously provided to `fs/watch`.
pub watch_id: String,
/// File or directory paths associated with this event.
pub changed_paths: Vec<AbsolutePathBuf>,
}

View File

@@ -0,0 +1,154 @@
use super::shared::v2_enum_from_core;
use codex_protocol::protocol::HookEventName as CoreHookEventName;
use codex_protocol::protocol::HookExecutionMode as CoreHookExecutionMode;
use codex_protocol::protocol::HookHandlerType as CoreHookHandlerType;
use codex_protocol::protocol::HookOutputEntry as CoreHookOutputEntry;
use codex_protocol::protocol::HookOutputEntryKind as CoreHookOutputEntryKind;
use codex_protocol::protocol::HookRunStatus as CoreHookRunStatus;
use codex_protocol::protocol::HookRunSummary as CoreHookRunSummary;
use codex_protocol::protocol::HookScope as CoreHookScope;
use codex_protocol::protocol::HookSource as CoreHookSource;
use codex_protocol::protocol::HookTrustStatus as CoreHookTrustStatus;
use codex_utils_absolute_path::AbsolutePathBuf;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use ts_rs::TS;
v2_enum_from_core!(
pub enum HookEventName from CoreHookEventName {
PreToolUse, PermissionRequest, PostToolUse, SessionStart, UserPromptSubmit, Stop
}
);
v2_enum_from_core!(
pub enum HookHandlerType from CoreHookHandlerType {
Command, Prompt, Agent
}
);
v2_enum_from_core!(
pub enum HookExecutionMode from CoreHookExecutionMode {
Sync, Async
}
);
v2_enum_from_core!(
pub enum HookScope from CoreHookScope {
Thread, Turn
}
);
v2_enum_from_core!(
pub enum HookSource from CoreHookSource {
System,
User,
Project,
Mdm,
SessionFlags,
Plugin,
CloudRequirements,
LegacyManagedConfigFile,
LegacyManagedConfigMdm,
Unknown,
}
);
v2_enum_from_core!(
pub enum HookTrustStatus from CoreHookTrustStatus {
Managed, Untrusted, Trusted, Modified
}
);
fn default_hook_source() -> HookSource {
HookSource::Unknown
}
v2_enum_from_core!(
pub enum HookRunStatus from CoreHookRunStatus {
Running, Completed, Failed, Blocked, Stopped
}
);
v2_enum_from_core!(
pub enum HookOutputEntryKind from CoreHookOutputEntryKind {
Warning, Stop, Feedback, Context, Error
}
);
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct HookOutputEntry {
pub kind: HookOutputEntryKind,
pub text: String,
}
impl From<CoreHookOutputEntry> for HookOutputEntry {
fn from(value: CoreHookOutputEntry) -> Self {
Self {
kind: value.kind.into(),
text: value.text,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct HookRunSummary {
pub id: String,
pub event_name: HookEventName,
pub handler_type: HookHandlerType,
pub execution_mode: HookExecutionMode,
pub scope: HookScope,
pub source_path: AbsolutePathBuf,
#[serde(default = "default_hook_source")]
pub source: HookSource,
pub display_order: i64,
pub status: HookRunStatus,
pub status_message: Option<String>,
pub started_at: i64,
pub completed_at: Option<i64>,
pub duration_ms: Option<i64>,
pub entries: Vec<HookOutputEntry>,
}
impl From<CoreHookRunSummary> for HookRunSummary {
fn from(value: CoreHookRunSummary) -> Self {
Self {
id: value.id,
event_name: value.event_name.into(),
handler_type: value.handler_type.into(),
execution_mode: value.execution_mode.into(),
scope: value.scope.into(),
source_path: value.source_path,
source: value.source.into(),
display_order: value.display_order,
status: value.status.into(),
status_message: value.status_message,
started_at: value.started_at,
completed_at: value.completed_at,
duration_ms: value.duration_ms,
entries: value.entries.into_iter().map(Into::into).collect(),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct HookStartedNotification {
pub thread_id: String,
pub turn_id: Option<String>,
pub run: HookRunSummary,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct HookCompletedNotification {
pub thread_id: String,
pub turn_id: Option<String>,
pub run: HookRunSummary,
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,703 @@
use super::shared::v2_enum_from_core;
use codex_protocol::approvals::ElicitationRequest as CoreElicitationRequest;
use codex_protocol::items::McpToolCallError as CoreMcpToolCallError;
use codex_protocol::mcp::CallToolResult as CoreMcpCallToolResult;
use codex_protocol::mcp::Resource as McpResource;
pub use codex_protocol::mcp::ResourceContent as McpResourceContent;
use codex_protocol::mcp::ResourceTemplate as McpResourceTemplate;
use codex_protocol::mcp::Tool as McpTool;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use serde_json::Value as JsonValue;
use std::collections::BTreeMap;
use ts_rs::TS;
v2_enum_from_core!(
pub enum McpAuthStatus from codex_protocol::protocol::McpAuthStatus {
Unsupported,
NotLoggedIn,
BearerToken,
OAuth
}
);
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ListMcpServerStatusParams {
/// Opaque pagination cursor returned by a previous call.
#[ts(optional = nullable)]
pub cursor: Option<String>,
/// Optional page size; defaults to a server-defined value.
#[ts(optional = nullable)]
pub limit: Option<u32>,
/// Controls how much MCP inventory data to fetch for each server.
/// Defaults to `Full` when omitted.
#[ts(optional = nullable)]
pub detail: Option<McpServerStatusDetail>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase", export_to = "v2/")]
pub enum McpServerStatusDetail {
Full,
ToolsAndAuthOnly,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct McpServerStatus {
pub name: String,
pub tools: std::collections::HashMap<String, McpTool>,
pub resources: Vec<McpResource>,
pub resource_templates: Vec<McpResourceTemplate>,
pub auth_status: McpAuthStatus,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ListMcpServerStatusResponse {
pub data: Vec<McpServerStatus>,
/// Opaque cursor to pass to the next call to continue after the last item.
/// If None, there are no more items to return.
pub next_cursor: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct McpResourceReadParams {
#[ts(optional = nullable)]
pub thread_id: Option<String>,
pub server: String,
pub uri: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct McpResourceReadResponse {
pub contents: Vec<McpResourceContent>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct McpServerToolCallParams {
pub thread_id: String,
pub server: String,
pub tool: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub arguments: Option<JsonValue>,
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub meta: Option<JsonValue>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct McpServerToolCallResponse {
pub content: Vec<JsonValue>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub structured_content: Option<JsonValue>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub is_error: Option<bool>,
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub meta: Option<JsonValue>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct McpToolCallResult {
// NOTE: `rmcp::model::Content` (and its `RawContent` variants) would be a more precise Rust
// representation of MCP content blocks. We intentionally use `serde_json::Value` here because
// this crate exports JSON schema + TS types (`schemars`/`ts-rs`), and the rmcp model types
// aren't set up to be schema/TS friendly (and would introduce heavier coupling to rmcp's Rust
// representations). Using `JsonValue` keeps the payload wire-shaped and easy to export.
pub content: Vec<JsonValue>,
pub structured_content: Option<JsonValue>,
#[serde(rename = "_meta")]
#[ts(rename = "_meta")]
pub meta: Option<JsonValue>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct McpToolCallError {
pub message: String,
}
impl From<CoreMcpCallToolResult> for McpServerToolCallResponse {
fn from(result: CoreMcpCallToolResult) -> Self {
Self {
content: result.content,
structured_content: result.structured_content,
is_error: result.is_error,
meta: result.meta,
}
}
}
impl From<CoreMcpCallToolResult> for McpToolCallResult {
fn from(result: CoreMcpCallToolResult) -> Self {
Self {
content: result.content,
structured_content: result.structured_content,
meta: result.meta,
}
}
}
impl From<CoreMcpToolCallError> for McpToolCallError {
fn from(error: CoreMcpToolCallError) -> Self {
Self {
message: error.message,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct McpServerRefreshParams {}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct McpServerRefreshResponse {}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct McpServerOauthLoginParams {
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional = nullable)]
pub scopes: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional = nullable)]
pub timeout_secs: Option<i64>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct McpServerOauthLoginResponse {
pub authorization_url: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct McpToolCallProgressNotification {
pub thread_id: String,
pub turn_id: String,
pub item_id: String,
pub message: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct McpServerOauthLoginCompletedNotification {
pub name: String,
pub success: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub error: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub enum McpServerStartupState {
Starting,
Ready,
Failed,
Cancelled,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct McpServerStatusUpdatedNotification {
pub name: String,
pub status: McpServerStartupState,
pub error: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub enum McpServerElicitationAction {
Accept,
Decline,
Cancel,
}
impl McpServerElicitationAction {
pub fn to_core(self) -> codex_protocol::approvals::ElicitationAction {
match self {
Self::Accept => codex_protocol::approvals::ElicitationAction::Accept,
Self::Decline => codex_protocol::approvals::ElicitationAction::Decline,
Self::Cancel => codex_protocol::approvals::ElicitationAction::Cancel,
}
}
}
impl From<McpServerElicitationAction> for rmcp::model::ElicitationAction {
fn from(value: McpServerElicitationAction) -> Self {
match value {
McpServerElicitationAction::Accept => Self::Accept,
McpServerElicitationAction::Decline => Self::Decline,
McpServerElicitationAction::Cancel => Self::Cancel,
}
}
}
impl From<rmcp::model::ElicitationAction> for McpServerElicitationAction {
fn from(value: rmcp::model::ElicitationAction) -> Self {
match value {
rmcp::model::ElicitationAction::Accept => Self::Accept,
rmcp::model::ElicitationAction::Decline => Self::Decline,
rmcp::model::ElicitationAction::Cancel => Self::Cancel,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct McpServerElicitationRequestParams {
pub thread_id: String,
/// Active Codex turn when this elicitation was observed, if app-server could correlate one.
///
/// This is nullable because MCP models elicitation as a standalone server-to-client request
/// identified by the MCP server request id. It may be triggered during a turn, but turn
/// context is app-server correlation rather than part of the protocol identity of the
/// elicitation itself.
pub turn_id: Option<String>,
pub server_name: String,
#[serde(flatten)]
pub request: McpServerElicitationRequest,
// TODO: When core can correlate an elicitation with an MCP tool call, expose the associated
// McpToolCall item id here as an optional field. The current core event does not carry that
// association.
}
/// Typed form schema for MCP `elicitation/create` requests.
///
/// This matches the `requestedSchema` shape from the MCP 2025-11-25
/// `ElicitRequestFormParams` schema.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[ts(export_to = "v2/")]
pub struct McpElicitationSchema {
#[serde(rename = "$schema", skip_serializing_if = "Option::is_none")]
#[ts(optional, rename = "$schema")]
pub schema_uri: Option<String>,
#[serde(rename = "type")]
#[ts(rename = "type")]
pub type_: McpElicitationObjectType,
pub properties: BTreeMap<String, McpElicitationPrimitiveSchema>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub required: Option<Vec<String>>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "lowercase")]
#[ts(export_to = "v2/")]
pub enum McpElicitationObjectType {
Object,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(untagged)]
#[ts(export_to = "v2/")]
pub enum McpElicitationPrimitiveSchema {
Enum(McpElicitationEnumSchema),
String(McpElicitationStringSchema),
Number(McpElicitationNumberSchema),
Boolean(McpElicitationBooleanSchema),
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[ts(export_to = "v2/")]
pub struct McpElicitationStringSchema {
#[serde(rename = "type")]
#[ts(rename = "type")]
pub type_: McpElicitationStringType,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub min_length: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub max_length: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub format: Option<McpElicitationStringFormat>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub default: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "lowercase")]
#[ts(export_to = "v2/")]
pub enum McpElicitationStringType {
String,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "kebab-case")]
#[ts(rename_all = "kebab-case", export_to = "v2/")]
pub enum McpElicitationStringFormat {
Email,
Uri,
Date,
DateTime,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[ts(export_to = "v2/")]
pub struct McpElicitationNumberSchema {
#[serde(rename = "type")]
#[ts(rename = "type")]
pub type_: McpElicitationNumberType,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub minimum: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub maximum: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub default: Option<f64>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "lowercase")]
#[ts(export_to = "v2/")]
pub enum McpElicitationNumberType {
Number,
Integer,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[ts(export_to = "v2/")]
pub struct McpElicitationBooleanSchema {
#[serde(rename = "type")]
#[ts(rename = "type")]
pub type_: McpElicitationBooleanType,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub default: Option<bool>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "lowercase")]
#[ts(export_to = "v2/")]
pub enum McpElicitationBooleanType {
Boolean,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(untagged)]
#[ts(export_to = "v2/")]
pub enum McpElicitationEnumSchema {
SingleSelect(McpElicitationSingleSelectEnumSchema),
MultiSelect(McpElicitationMultiSelectEnumSchema),
Legacy(McpElicitationLegacyTitledEnumSchema),
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[ts(export_to = "v2/")]
pub struct McpElicitationLegacyTitledEnumSchema {
#[serde(rename = "type")]
#[ts(rename = "type")]
pub type_: McpElicitationStringType,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub description: Option<String>,
#[serde(rename = "enum")]
#[ts(rename = "enum")]
pub enum_: Vec<String>,
#[serde(rename = "enumNames", skip_serializing_if = "Option::is_none")]
#[ts(optional, rename = "enumNames")]
pub enum_names: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub default: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(untagged)]
#[ts(export_to = "v2/")]
pub enum McpElicitationSingleSelectEnumSchema {
Untitled(McpElicitationUntitledSingleSelectEnumSchema),
Titled(McpElicitationTitledSingleSelectEnumSchema),
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[ts(export_to = "v2/")]
pub struct McpElicitationUntitledSingleSelectEnumSchema {
#[serde(rename = "type")]
#[ts(rename = "type")]
pub type_: McpElicitationStringType,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub description: Option<String>,
#[serde(rename = "enum")]
#[ts(rename = "enum")]
pub enum_: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub default: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[ts(export_to = "v2/")]
pub struct McpElicitationTitledSingleSelectEnumSchema {
#[serde(rename = "type")]
#[ts(rename = "type")]
pub type_: McpElicitationStringType,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub description: Option<String>,
#[serde(rename = "oneOf")]
#[ts(rename = "oneOf")]
pub one_of: Vec<McpElicitationConstOption>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub default: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(untagged)]
#[ts(export_to = "v2/")]
pub enum McpElicitationMultiSelectEnumSchema {
Untitled(McpElicitationUntitledMultiSelectEnumSchema),
Titled(McpElicitationTitledMultiSelectEnumSchema),
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[ts(export_to = "v2/")]
pub struct McpElicitationUntitledMultiSelectEnumSchema {
#[serde(rename = "type")]
#[ts(rename = "type")]
pub type_: McpElicitationArrayType,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub min_items: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub max_items: Option<u64>,
pub items: McpElicitationUntitledEnumItems,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub default: Option<Vec<String>>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[ts(export_to = "v2/")]
pub struct McpElicitationTitledMultiSelectEnumSchema {
#[serde(rename = "type")]
#[ts(rename = "type")]
pub type_: McpElicitationArrayType,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub min_items: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub max_items: Option<u64>,
pub items: McpElicitationTitledEnumItems,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub default: Option<Vec<String>>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "lowercase")]
#[ts(export_to = "v2/")]
pub enum McpElicitationArrayType {
Array,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(deny_unknown_fields)]
#[ts(export_to = "v2/")]
pub struct McpElicitationUntitledEnumItems {
#[serde(rename = "type")]
#[ts(rename = "type")]
pub type_: McpElicitationStringType,
#[serde(rename = "enum")]
#[ts(rename = "enum")]
pub enum_: Vec<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(deny_unknown_fields)]
#[ts(export_to = "v2/")]
pub struct McpElicitationTitledEnumItems {
#[serde(rename = "anyOf", alias = "oneOf")]
#[ts(rename = "anyOf")]
pub any_of: Vec<McpElicitationConstOption>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(deny_unknown_fields)]
#[ts(export_to = "v2/")]
pub struct McpElicitationConstOption {
#[serde(rename = "const")]
#[ts(rename = "const")]
pub const_: String,
pub title: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(tag = "mode", rename_all = "camelCase")]
#[ts(tag = "mode")]
#[ts(export_to = "v2/")]
pub enum McpServerElicitationRequest {
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
Form {
#[serde(rename = "_meta")]
#[ts(rename = "_meta")]
meta: Option<JsonValue>,
message: String,
requested_schema: McpElicitationSchema,
},
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
Url {
#[serde(rename = "_meta")]
#[ts(rename = "_meta")]
meta: Option<JsonValue>,
message: String,
url: String,
elicitation_id: String,
},
}
impl TryFrom<CoreElicitationRequest> for McpServerElicitationRequest {
type Error = serde_json::Error;
fn try_from(value: CoreElicitationRequest) -> Result<Self, Self::Error> {
match value {
CoreElicitationRequest::Form {
meta,
message,
requested_schema,
} => Ok(Self::Form {
meta,
message,
requested_schema: serde_json::from_value(requested_schema)?,
}),
CoreElicitationRequest::Url {
meta,
message,
url,
elicitation_id,
} => Ok(Self::Url {
meta,
message,
url,
elicitation_id,
}),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct McpServerElicitationRequestResponse {
pub action: McpServerElicitationAction,
/// Structured user input for accepted elicitations, mirroring RMCP `CreateElicitationResult`.
///
/// This is nullable because decline/cancel responses have no content.
pub content: Option<JsonValue>,
/// Optional client metadata for form-mode action handling.
#[serde(rename = "_meta")]
#[ts(rename = "_meta")]
pub meta: Option<JsonValue>,
}
impl From<McpServerElicitationRequestResponse> for rmcp::model::CreateElicitationResult {
fn from(value: McpServerElicitationRequestResponse) -> Self {
Self {
action: value.action.into(),
content: value.content,
}
}
}
impl From<rmcp::model::CreateElicitationResult> for McpServerElicitationRequestResponse {
fn from(value: rmcp::model::CreateElicitationResult) -> Self {
Self {
action: value.action.into(),
content: value.content,
meta: None,
}
}
}

View File

@@ -0,0 +1,53 @@
mod shared;
mod account;
mod apps;
mod collaboration_mode;
mod command_exec;
mod config;
mod device_key;
mod experimental_feature;
mod feedback;
mod fs;
mod hook;
mod item;
mod mcp;
mod model;
mod notification;
mod permissions;
mod plugin;
mod process;
mod realtime;
mod review;
mod thread;
mod thread_data;
mod turn;
mod windows_sandbox;
pub use account::*;
pub use apps::*;
pub use collaboration_mode::*;
pub use command_exec::*;
pub use config::*;
pub use device_key::*;
pub use experimental_feature::*;
pub use feedback::*;
pub use fs::*;
pub use hook::*;
pub use item::*;
pub use mcp::*;
pub use model::*;
pub use notification::*;
pub use permissions::*;
pub use plugin::*;
pub use process::*;
pub use realtime::*;
pub use review::*;
pub use shared::*;
pub use thread::*;
pub use thread_data::*;
pub use turn::*;
pub use windows_sandbox::*;
#[cfg(test)]
mod tests;

View File

@@ -0,0 +1,151 @@
use super::shared::v2_enum_from_core;
use codex_protocol::openai_models::InputModality;
use codex_protocol::openai_models::ModelAvailabilityNux as CoreModelAvailabilityNux;
use codex_protocol::openai_models::ReasoningEffort;
use codex_protocol::openai_models::default_input_modalities;
use codex_protocol::protocol::ModelRerouteReason as CoreModelRerouteReason;
use codex_protocol::protocol::ModelVerification as CoreModelVerification;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use ts_rs::TS;
v2_enum_from_core!(
pub enum ModelRerouteReason from CoreModelRerouteReason {
HighRiskCyberActivity
}
);
v2_enum_from_core!(
pub enum ModelVerification from CoreModelVerification {
TrustedAccessForCyber
}
);
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ModelProviderCapabilitiesReadParams {}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ModelProviderCapabilitiesReadResponse {
pub namespace_tools: bool,
pub image_generation: bool,
pub web_search: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ModelListParams {
/// Opaque pagination cursor returned by a previous call.
#[ts(optional = nullable)]
pub cursor: Option<String>,
/// Optional page size; defaults to a reasonable server-side value.
#[ts(optional = nullable)]
pub limit: Option<u32>,
/// When true, include models that are hidden from the default picker list.
#[ts(optional = nullable)]
pub include_hidden: Option<bool>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ModelAvailabilityNux {
pub message: String,
}
impl From<CoreModelAvailabilityNux> for ModelAvailabilityNux {
fn from(value: CoreModelAvailabilityNux) -> Self {
Self {
message: value.message,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ModelServiceTier {
pub id: String,
pub name: String,
pub description: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct Model {
pub id: String,
pub model: String,
pub upgrade: Option<String>,
pub upgrade_info: Option<ModelUpgradeInfo>,
pub availability_nux: Option<ModelAvailabilityNux>,
pub display_name: String,
pub description: String,
pub hidden: bool,
pub supported_reasoning_efforts: Vec<ReasoningEffortOption>,
pub default_reasoning_effort: ReasoningEffort,
#[serde(default = "default_input_modalities")]
pub input_modalities: Vec<InputModality>,
#[serde(default)]
pub supports_personality: bool,
/// Deprecated: use `serviceTiers` instead.
#[serde(default)]
pub additional_speed_tiers: Vec<String>,
#[serde(default)]
pub service_tiers: Vec<ModelServiceTier>,
// Only one model should be marked as default.
pub is_default: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ModelUpgradeInfo {
pub model: String,
pub upgrade_copy: Option<String>,
pub model_link: Option<String>,
pub migration_markdown: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ReasoningEffortOption {
pub reasoning_effort: ReasoningEffort,
pub description: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ModelListResponse {
pub data: Vec<Model>,
/// Opaque cursor to pass to the next call to continue after the last item.
/// If None, there are no more items to return.
pub next_cursor: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ModelReroutedNotification {
pub thread_id: String,
pub turn_id: String,
pub from_model: String,
pub to_model: String,
pub reason: ModelRerouteReason,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ModelVerificationNotification {
pub thread_id: String,
pub turn_id: String,
pub verifications: Vec<ModelVerification>,
}

View File

@@ -0,0 +1,56 @@
use super::TurnError;
use crate::RequestId;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use ts_rs::TS;
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct DeprecationNoticeNotification {
/// Concise summary of what is deprecated.
pub summary: String,
/// Optional extra guidance, such as migration steps or rationale.
pub details: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct WarningNotification {
/// Optional thread target when the warning applies to a specific thread.
pub thread_id: Option<String>,
/// Concise warning message for the user.
pub message: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct GuardianWarningNotification {
/// Thread target for the guardian warning.
pub thread_id: String,
/// Concise guardian warning message for the user.
pub message: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ErrorNotification {
pub error: TurnError,
// Set to true if the error is transient and the app-server process will automatically retry.
// If true, this will not interrupt a turn.
pub will_retry: bool,
pub thread_id: String,
pub turn_id: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ServerRequestResolvedNotification {
pub thread_id: String,
pub request_id: RequestId,
}

View File

@@ -0,0 +1,854 @@
use super::shared::v2_enum_from_core;
use codex_protocol::approvals::ExecPolicyAmendment as CoreExecPolicyAmendment;
use codex_protocol::approvals::NetworkApprovalContext as CoreNetworkApprovalContext;
use codex_protocol::approvals::NetworkApprovalProtocol as CoreNetworkApprovalProtocol;
use codex_protocol::approvals::NetworkPolicyAmendment as CoreNetworkPolicyAmendment;
use codex_protocol::approvals::NetworkPolicyRuleAction as CoreNetworkPolicyRuleAction;
use codex_protocol::models::ActivePermissionProfile as CoreActivePermissionProfile;
use codex_protocol::models::ActivePermissionProfileModification as CoreActivePermissionProfileModification;
use codex_protocol::models::AdditionalPermissionProfile as CoreAdditionalPermissionProfile;
use codex_protocol::models::FileSystemPermissions as CoreFileSystemPermissions;
use codex_protocol::models::ManagedFileSystemPermissions as CoreManagedFileSystemPermissions;
use codex_protocol::models::NetworkPermissions as CoreNetworkPermissions;
use codex_protocol::models::PermissionProfile as CorePermissionProfile;
use codex_protocol::permissions::FileSystemAccessMode as CoreFileSystemAccessMode;
use codex_protocol::permissions::FileSystemPath as CoreFileSystemPath;
use codex_protocol::permissions::FileSystemSandboxEntry as CoreFileSystemSandboxEntry;
use codex_protocol::permissions::FileSystemSpecialPath as CoreFileSystemSpecialPath;
use codex_protocol::permissions::NetworkSandboxPolicy as CoreNetworkSandboxPolicy;
use codex_protocol::protocol::NetworkAccess as CoreNetworkAccess;
use codex_protocol::request_permissions::PermissionGrantScope as CorePermissionGrantScope;
use codex_protocol::request_permissions::RequestPermissionProfile as CoreRequestPermissionProfile;
use codex_utils_absolute_path::AbsolutePathBuf;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::num::NonZeroUsize;
use std::path::PathBuf;
use ts_rs::TS;
v2_enum_from_core! {
pub enum NetworkApprovalProtocol from CoreNetworkApprovalProtocol {
Http,
Https,
Socks5Tcp,
Socks5Udp,
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct NetworkApprovalContext {
pub host: String,
pub protocol: NetworkApprovalProtocol,
}
impl From<CoreNetworkApprovalContext> for NetworkApprovalContext {
fn from(value: CoreNetworkApprovalContext) -> Self {
Self {
host: value.host,
protocol: value.protocol.into(),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct AdditionalFileSystemPermissions {
/// This will be removed in favor of `entries`.
pub read: Option<Vec<AbsolutePathBuf>>,
/// This will be removed in favor of `entries`.
pub write: Option<Vec<AbsolutePathBuf>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub glob_scan_max_depth: Option<NonZeroUsize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub entries: Option<Vec<FileSystemSandboxEntry>>,
}
impl From<CoreFileSystemPermissions> for AdditionalFileSystemPermissions {
fn from(value: CoreFileSystemPermissions) -> Self {
if let Some((read, write)) = value.legacy_read_write_roots() {
let mut entries = Vec::with_capacity(
read.as_ref().map_or(0, Vec::len) + write.as_ref().map_or(0, Vec::len),
);
if let Some(paths) = read.as_ref() {
entries.extend(paths.iter().map(|path| FileSystemSandboxEntry {
path: FileSystemPath::Path { path: path.clone() },
access: FileSystemAccessMode::Read,
}));
}
if let Some(paths) = write.as_ref() {
entries.extend(paths.iter().map(|path| FileSystemSandboxEntry {
path: FileSystemPath::Path { path: path.clone() },
access: FileSystemAccessMode::Write,
}));
}
Self {
read,
write,
glob_scan_max_depth: None,
entries: Some(entries),
}
} else {
Self {
read: None,
write: None,
glob_scan_max_depth: value.glob_scan_max_depth,
entries: Some(
value
.entries
.into_iter()
.map(FileSystemSandboxEntry::from)
.collect(),
),
}
}
}
}
impl From<AdditionalFileSystemPermissions> for CoreFileSystemPermissions {
fn from(value: AdditionalFileSystemPermissions) -> Self {
let mut permissions = if let Some(entries) = value.entries {
Self {
entries: entries
.into_iter()
.map(CoreFileSystemSandboxEntry::from)
.collect(),
glob_scan_max_depth: None,
}
} else {
CoreFileSystemPermissions::from_read_write_roots(value.read, value.write)
};
permissions.glob_scan_max_depth = value.glob_scan_max_depth;
permissions
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct AdditionalNetworkPermissions {
pub enabled: Option<bool>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PermissionProfileNetworkPermissions {
pub enabled: bool,
}
impl From<CoreNetworkPermissions> for AdditionalNetworkPermissions {
fn from(value: CoreNetworkPermissions) -> Self {
Self {
enabled: value.enabled,
}
}
}
impl From<AdditionalNetworkPermissions> for CoreNetworkPermissions {
fn from(value: AdditionalNetworkPermissions) -> Self {
Self {
enabled: value.enabled,
}
}
}
impl From<CoreNetworkSandboxPolicy> for PermissionProfileNetworkPermissions {
fn from(value: CoreNetworkSandboxPolicy) -> Self {
Self {
enabled: value.is_enabled(),
}
}
}
impl From<PermissionProfileNetworkPermissions> for CoreNetworkSandboxPolicy {
fn from(value: PermissionProfileNetworkPermissions) -> Self {
if value.enabled {
Self::Enabled
} else {
Self::Restricted
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
#[ts(export_to = "v2/")]
pub struct RequestPermissionProfile {
pub network: Option<AdditionalNetworkPermissions>,
pub file_system: Option<AdditionalFileSystemPermissions>,
}
impl From<CoreRequestPermissionProfile> for RequestPermissionProfile {
fn from(value: CoreRequestPermissionProfile) -> Self {
Self {
network: value.network.map(AdditionalNetworkPermissions::from),
file_system: value.file_system.map(AdditionalFileSystemPermissions::from),
}
}
}
impl From<RequestPermissionProfile> for CoreRequestPermissionProfile {
fn from(value: RequestPermissionProfile) -> Self {
Self {
network: value.network.map(CoreNetworkPermissions::from),
file_system: value.file_system.map(CoreFileSystemPermissions::from),
}
}
}
v2_enum_from_core!(
pub enum FileSystemAccessMode from CoreFileSystemAccessMode {
Read,
Write,
None
}
);
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(tag = "kind", rename_all = "snake_case")]
#[ts(tag = "kind")]
#[ts(export_to = "v2/")]
pub enum FileSystemSpecialPath {
Root,
Minimal,
#[serde(alias = "current_working_directory")]
ProjectRoots {
subpath: Option<PathBuf>,
},
Tmpdir,
SlashTmp,
Unknown {
path: String,
subpath: Option<PathBuf>,
},
}
impl From<CoreFileSystemSpecialPath> for FileSystemSpecialPath {
fn from(value: CoreFileSystemSpecialPath) -> Self {
match value {
CoreFileSystemSpecialPath::Root => Self::Root,
CoreFileSystemSpecialPath::Minimal => Self::Minimal,
CoreFileSystemSpecialPath::ProjectRoots { subpath } => Self::ProjectRoots { subpath },
CoreFileSystemSpecialPath::Tmpdir => Self::Tmpdir,
CoreFileSystemSpecialPath::SlashTmp => Self::SlashTmp,
CoreFileSystemSpecialPath::Unknown { path, subpath } => Self::Unknown { path, subpath },
}
}
}
impl From<FileSystemSpecialPath> for CoreFileSystemSpecialPath {
fn from(value: FileSystemSpecialPath) -> Self {
match value {
FileSystemSpecialPath::Root => Self::Root,
FileSystemSpecialPath::Minimal => Self::Minimal,
FileSystemSpecialPath::ProjectRoots { subpath } => Self::ProjectRoots { subpath },
FileSystemSpecialPath::Tmpdir => Self::Tmpdir,
FileSystemSpecialPath::SlashTmp => Self::SlashTmp,
FileSystemSpecialPath::Unknown { path, subpath } => Self::Unknown { path, subpath },
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "snake_case")]
#[ts(tag = "type")]
#[ts(export_to = "v2/")]
pub enum FileSystemPath {
Path { path: AbsolutePathBuf },
GlobPattern { pattern: String },
Special { value: FileSystemSpecialPath },
}
impl From<CoreFileSystemPath> for FileSystemPath {
fn from(value: CoreFileSystemPath) -> Self {
match value {
CoreFileSystemPath::Path { path } => Self::Path { path },
CoreFileSystemPath::GlobPattern { pattern } => Self::GlobPattern { pattern },
CoreFileSystemPath::Special { value } => Self::Special {
value: value.into(),
},
}
}
}
impl From<FileSystemPath> for CoreFileSystemPath {
fn from(value: FileSystemPath) -> Self {
match value {
FileSystemPath::Path { path } => Self::Path { path },
FileSystemPath::GlobPattern { pattern } => Self::GlobPattern { pattern },
FileSystemPath::Special { value } => Self::Special {
value: value.into(),
},
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct FileSystemSandboxEntry {
pub path: FileSystemPath,
pub access: FileSystemAccessMode,
}
impl From<CoreFileSystemSandboxEntry> for FileSystemSandboxEntry {
fn from(value: CoreFileSystemSandboxEntry) -> Self {
Self {
path: value.path.into(),
access: value.access.into(),
}
}
}
impl From<FileSystemSandboxEntry> for CoreFileSystemSandboxEntry {
fn from(value: FileSystemSandboxEntry) -> Self {
Self {
path: value.path.into(),
access: value.access.to_core(),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "camelCase")]
#[ts(tag = "type")]
#[ts(export_to = "v2/")]
pub enum PermissionProfileFileSystemPermissions {
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
Restricted {
entries: Vec<FileSystemSandboxEntry>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
glob_scan_max_depth: Option<NonZeroUsize>,
},
Unrestricted,
}
impl From<CoreManagedFileSystemPermissions> for PermissionProfileFileSystemPermissions {
fn from(value: CoreManagedFileSystemPermissions) -> Self {
match value {
CoreManagedFileSystemPermissions::Restricted {
entries,
glob_scan_max_depth,
} => Self::Restricted {
entries: entries
.into_iter()
.map(FileSystemSandboxEntry::from)
.collect(),
glob_scan_max_depth,
},
CoreManagedFileSystemPermissions::Unrestricted => Self::Unrestricted,
}
}
}
impl From<PermissionProfileFileSystemPermissions> for CoreManagedFileSystemPermissions {
fn from(value: PermissionProfileFileSystemPermissions) -> Self {
match value {
PermissionProfileFileSystemPermissions::Restricted {
entries,
glob_scan_max_depth,
} => Self::Restricted {
entries: entries
.into_iter()
.map(CoreFileSystemSandboxEntry::from)
.collect(),
glob_scan_max_depth,
},
PermissionProfileFileSystemPermissions::Unrestricted => Self::Unrestricted,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "camelCase")]
#[ts(tag = "type")]
#[ts(export_to = "v2/")]
pub enum PermissionProfile {
/// Codex owns sandbox construction for this profile.
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
Managed {
network: PermissionProfileNetworkPermissions,
file_system: PermissionProfileFileSystemPermissions,
},
/// Do not apply an outer sandbox.
Disabled,
/// Filesystem isolation is enforced by an external caller.
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
External {
network: PermissionProfileNetworkPermissions,
},
}
impl From<CorePermissionProfile> for PermissionProfile {
fn from(value: CorePermissionProfile) -> Self {
match value {
CorePermissionProfile::Managed {
file_system,
network,
} => Self::Managed {
network: network.into(),
file_system: file_system.into(),
},
CorePermissionProfile::Disabled => Self::Disabled,
CorePermissionProfile::External { network } => Self::External {
network: network.into(),
},
}
}
}
impl From<PermissionProfile> for CorePermissionProfile {
fn from(value: PermissionProfile) -> Self {
match value {
PermissionProfile::Managed {
file_system,
network,
} => Self::Managed {
file_system: file_system.into(),
network: network.into(),
},
PermissionProfile::Disabled => Self::Disabled,
PermissionProfile::External { network } => Self::External {
network: network.into(),
},
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ActivePermissionProfile {
/// Identifier from `default_permissions` or the implicit built-in default,
/// such as `:workspace` or a user-defined `[permissions.<id>]` profile.
pub id: String,
/// Parent profile identifier once permissions profiles support
/// inheritance. This is currently always `null`.
#[serde(default)]
pub extends: Option<String>,
/// Bounded user-requested modifications applied on top of the named
/// profile, if any.
#[serde(default)]
pub modifications: Vec<ActivePermissionProfileModification>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "camelCase")]
#[ts(tag = "type")]
#[ts(export_to = "v2/")]
pub enum ActivePermissionProfileModification {
/// Additional concrete directory that should be writable.
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
AdditionalWritableRoot { path: AbsolutePathBuf },
}
impl From<CoreActivePermissionProfileModification> for ActivePermissionProfileModification {
fn from(value: CoreActivePermissionProfileModification) -> Self {
match value {
CoreActivePermissionProfileModification::AdditionalWritableRoot { path } => {
Self::AdditionalWritableRoot { path }
}
}
}
}
impl From<ActivePermissionProfileModification> for CoreActivePermissionProfileModification {
fn from(value: ActivePermissionProfileModification) -> Self {
match value {
ActivePermissionProfileModification::AdditionalWritableRoot { path } => {
Self::AdditionalWritableRoot { path }
}
}
}
}
impl From<CoreActivePermissionProfile> for ActivePermissionProfile {
fn from(value: CoreActivePermissionProfile) -> Self {
Self {
id: value.id,
extends: value.extends,
modifications: value
.modifications
.into_iter()
.map(ActivePermissionProfileModification::from)
.collect(),
}
}
}
impl From<ActivePermissionProfile> for CoreActivePermissionProfile {
fn from(value: ActivePermissionProfile) -> Self {
Self {
id: value.id,
extends: value.extends,
modifications: value
.modifications
.into_iter()
.map(CoreActivePermissionProfileModification::from)
.collect(),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "camelCase")]
#[ts(tag = "type")]
#[ts(export_to = "v2/")]
pub enum PermissionProfileSelectionParams {
/// Select a named built-in or user-defined profile and optionally apply
/// bounded modifications that Codex knows how to validate.
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
Profile {
id: String,
#[ts(optional = nullable)]
modifications: Option<Vec<PermissionProfileModificationParams>>,
},
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "camelCase")]
#[ts(tag = "type")]
#[ts(export_to = "v2/")]
pub enum PermissionProfileModificationParams {
/// Additional concrete directory that should be writable.
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
AdditionalWritableRoot { path: AbsolutePathBuf },
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct AdditionalPermissionProfile {
/// Partial overlay used for per-command permission requests.
pub network: Option<AdditionalNetworkPermissions>,
pub file_system: Option<AdditionalFileSystemPermissions>,
}
impl From<CoreAdditionalPermissionProfile> for AdditionalPermissionProfile {
fn from(value: CoreAdditionalPermissionProfile) -> Self {
Self {
network: value.network.map(AdditionalNetworkPermissions::from),
file_system: value.file_system.map(AdditionalFileSystemPermissions::from),
}
}
}
impl From<AdditionalPermissionProfile> for CoreAdditionalPermissionProfile {
fn from(value: AdditionalPermissionProfile) -> Self {
Self {
network: value.network.map(CoreNetworkPermissions::from),
file_system: value.file_system.map(CoreFileSystemPermissions::from),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct GrantedPermissionProfile {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub network: Option<AdditionalNetworkPermissions>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub file_system: Option<AdditionalFileSystemPermissions>,
}
impl From<GrantedPermissionProfile> for CoreAdditionalPermissionProfile {
fn from(value: GrantedPermissionProfile) -> Self {
Self {
network: value.network.map(CoreNetworkPermissions::from),
file_system: value.file_system.map(CoreFileSystemPermissions::from),
}
}
}
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub enum NetworkAccess {
#[default]
Restricted,
Enabled,
}
#[derive(Serialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "camelCase")]
#[ts(tag = "type")]
#[ts(export_to = "v2/")]
pub enum SandboxPolicy {
DangerFullAccess,
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
ReadOnly {
#[serde(default)]
network_access: bool,
},
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
ExternalSandbox {
#[serde(default)]
network_access: NetworkAccess,
},
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
WorkspaceWrite {
#[serde(default)]
writable_roots: Vec<AbsolutePathBuf>,
#[serde(default)]
network_access: bool,
#[serde(default)]
exclude_tmpdir_env_var: bool,
#[serde(default)]
exclude_slash_tmp: bool,
},
}
#[derive(Deserialize)]
#[serde(tag = "type", rename_all = "camelCase")]
enum SandboxPolicyDeserialize {
DangerFullAccess,
#[serde(rename_all = "camelCase")]
ReadOnly {
#[serde(default)]
network_access: bool,
#[serde(default)]
access: Option<LegacyReadOnlyAccess>,
},
#[serde(rename_all = "camelCase")]
ExternalSandbox {
#[serde(default)]
network_access: NetworkAccess,
},
#[serde(rename_all = "camelCase")]
WorkspaceWrite {
#[serde(default)]
writable_roots: Vec<AbsolutePathBuf>,
#[serde(default)]
read_only_access: Option<LegacyReadOnlyAccess>,
#[serde(default)]
network_access: bool,
#[serde(default)]
exclude_tmpdir_env_var: bool,
#[serde(default)]
exclude_slash_tmp: bool,
},
}
#[derive(Deserialize)]
#[serde(tag = "type", rename_all = "camelCase")]
enum LegacyReadOnlyAccess {
FullAccess,
Restricted,
}
impl<'de> Deserialize<'de> for SandboxPolicy {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
match SandboxPolicyDeserialize::deserialize(deserializer)? {
SandboxPolicyDeserialize::DangerFullAccess => Ok(SandboxPolicy::DangerFullAccess),
SandboxPolicyDeserialize::ReadOnly {
network_access,
access,
} => {
if matches!(access, Some(LegacyReadOnlyAccess::Restricted)) {
return Err(serde::de::Error::custom(
"readOnly.access is no longer supported; use permissionProfile for restricted reads",
));
}
Ok(SandboxPolicy::ReadOnly { network_access })
}
SandboxPolicyDeserialize::ExternalSandbox { network_access } => {
Ok(SandboxPolicy::ExternalSandbox { network_access })
}
SandboxPolicyDeserialize::WorkspaceWrite {
writable_roots,
read_only_access,
network_access,
exclude_tmpdir_env_var,
exclude_slash_tmp,
} => {
if matches!(read_only_access, Some(LegacyReadOnlyAccess::Restricted)) {
return Err(serde::de::Error::custom(
"workspaceWrite.readOnlyAccess is no longer supported; use permissionProfile for restricted reads",
));
}
Ok(SandboxPolicy::WorkspaceWrite {
writable_roots,
network_access,
exclude_tmpdir_env_var,
exclude_slash_tmp,
})
}
}
}
}
impl SandboxPolicy {
pub fn to_core(&self) -> codex_protocol::protocol::SandboxPolicy {
match self {
SandboxPolicy::DangerFullAccess => {
codex_protocol::protocol::SandboxPolicy::DangerFullAccess
}
SandboxPolicy::ReadOnly { network_access } => {
codex_protocol::protocol::SandboxPolicy::ReadOnly {
network_access: *network_access,
}
}
SandboxPolicy::ExternalSandbox { network_access } => {
codex_protocol::protocol::SandboxPolicy::ExternalSandbox {
network_access: match network_access {
NetworkAccess::Restricted => CoreNetworkAccess::Restricted,
NetworkAccess::Enabled => CoreNetworkAccess::Enabled,
},
}
}
SandboxPolicy::WorkspaceWrite {
writable_roots,
network_access,
exclude_tmpdir_env_var,
exclude_slash_tmp,
} => codex_protocol::protocol::SandboxPolicy::WorkspaceWrite {
writable_roots: writable_roots.clone(),
network_access: *network_access,
exclude_tmpdir_env_var: *exclude_tmpdir_env_var,
exclude_slash_tmp: *exclude_slash_tmp,
},
}
}
}
impl From<codex_protocol::protocol::SandboxPolicy> for SandboxPolicy {
fn from(value: codex_protocol::protocol::SandboxPolicy) -> Self {
match value {
codex_protocol::protocol::SandboxPolicy::DangerFullAccess => {
SandboxPolicy::DangerFullAccess
}
codex_protocol::protocol::SandboxPolicy::ReadOnly { network_access } => {
SandboxPolicy::ReadOnly { network_access }
}
codex_protocol::protocol::SandboxPolicy::ExternalSandbox { network_access } => {
SandboxPolicy::ExternalSandbox {
network_access: match network_access {
CoreNetworkAccess::Restricted => NetworkAccess::Restricted,
CoreNetworkAccess::Enabled => NetworkAccess::Enabled,
},
}
}
codex_protocol::protocol::SandboxPolicy::WorkspaceWrite {
writable_roots,
network_access,
exclude_tmpdir_env_var,
exclude_slash_tmp,
} => SandboxPolicy::WorkspaceWrite {
writable_roots,
network_access,
exclude_tmpdir_env_var,
exclude_slash_tmp,
},
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(transparent)]
#[ts(type = "Array<string>", export_to = "v2/")]
pub struct ExecPolicyAmendment {
pub command: Vec<String>,
}
impl ExecPolicyAmendment {
pub fn into_core(self) -> CoreExecPolicyAmendment {
CoreExecPolicyAmendment::new(self.command)
}
}
impl From<CoreExecPolicyAmendment> for ExecPolicyAmendment {
fn from(value: CoreExecPolicyAmendment) -> Self {
Self {
command: value.command().to_vec(),
}
}
}
v2_enum_from_core!(
pub enum NetworkPolicyRuleAction from CoreNetworkPolicyRuleAction {
Allow, Deny
}
);
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct NetworkPolicyAmendment {
pub host: String,
pub action: NetworkPolicyRuleAction,
}
impl NetworkPolicyAmendment {
pub fn into_core(self) -> CoreNetworkPolicyAmendment {
CoreNetworkPolicyAmendment {
host: self.host,
action: self.action.to_core(),
}
}
}
impl From<CoreNetworkPolicyAmendment> for NetworkPolicyAmendment {
fn from(value: CoreNetworkPolicyAmendment) -> Self {
Self {
host: value.host,
action: NetworkPolicyRuleAction::from(value.action),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PermissionsRequestApprovalParams {
pub thread_id: String,
pub turn_id: String,
pub item_id: String,
pub cwd: AbsolutePathBuf,
pub reason: Option<String>,
pub permissions: RequestPermissionProfile,
}
v2_enum_from_core!(
#[derive(Default)]
pub enum PermissionGrantScope from CorePermissionGrantScope {
#[default]
Turn,
Session
}
);
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PermissionsRequestApprovalResponse {
pub permissions: GrantedPermissionProfile,
#[serde(default)]
pub scope: PermissionGrantScope,
/// Review every subsequent command in this turn before normal sandboxed execution.
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub strict_auto_review: Option<bool>,
}

View File

@@ -0,0 +1,715 @@
use super::AppSummary;
use super::HookEventName;
use super::HookHandlerType;
use super::HookSource;
use super::HookTrustStatus;
use codex_protocol::protocol::SkillDependencies as CoreSkillDependencies;
use codex_protocol::protocol::SkillInterface as CoreSkillInterface;
use codex_protocol::protocol::SkillMetadata as CoreSkillMetadata;
use codex_protocol::protocol::SkillScope as CoreSkillScope;
use codex_protocol::protocol::SkillToolDependency as CoreSkillToolDependency;
use codex_utils_absolute_path::AbsolutePathBuf;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::path::PathBuf;
use ts_rs::TS;
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct SkillsListParams {
/// When empty, defaults to the current session working directory.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub cwds: Vec<PathBuf>,
/// When true, bypass the skills cache and re-scan skills from disk.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub force_reload: bool,
/// Optional per-cwd extra roots to scan as user-scoped skills.
#[serde(default)]
#[ts(optional = nullable)]
pub per_cwd_extra_user_roots: Option<Vec<SkillsListExtraRootsForCwd>>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct SkillsListExtraRootsForCwd {
pub cwd: PathBuf,
pub extra_user_roots: Vec<PathBuf>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct SkillsListResponse {
pub data: Vec<SkillsListEntry>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct HooksListParams {
/// When empty, defaults to the current session working directory.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub cwds: Vec<PathBuf>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct HooksListResponse {
pub data: Vec<HooksListEntry>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct MarketplaceAddParams {
pub source: String,
#[ts(optional = nullable)]
pub ref_name: Option<String>,
#[ts(optional = nullable)]
pub sparse_paths: Option<Vec<String>>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct MarketplaceAddResponse {
pub marketplace_name: String,
pub installed_root: AbsolutePathBuf,
pub already_added: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct MarketplaceRemoveParams {
pub marketplace_name: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct MarketplaceRemoveResponse {
pub marketplace_name: String,
pub installed_root: Option<AbsolutePathBuf>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct MarketplaceUpgradeParams {
#[ts(optional = nullable)]
pub marketplace_name: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct MarketplaceUpgradeResponse {
pub selected_marketplaces: Vec<String>,
pub upgraded_roots: Vec<AbsolutePathBuf>,
pub errors: Vec<MarketplaceUpgradeErrorInfo>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct MarketplaceUpgradeErrorInfo {
pub marketplace_name: String,
pub message: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginListParams {
/// Optional working directories used to discover repo marketplaces. When omitted,
/// only home-scoped marketplaces and the official curated marketplace are considered.
#[ts(optional = nullable)]
pub cwds: Option<Vec<AbsolutePathBuf>>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginListResponse {
pub marketplaces: Vec<PluginMarketplaceEntry>,
#[serde(default)]
pub marketplace_load_errors: Vec<MarketplaceLoadErrorInfo>,
#[serde(default)]
pub featured_plugin_ids: Vec<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct MarketplaceLoadErrorInfo {
pub marketplace_path: AbsolutePathBuf,
pub message: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginReadParams {
#[ts(optional = nullable)]
pub marketplace_path: Option<AbsolutePathBuf>,
#[ts(optional = nullable)]
pub remote_marketplace_name: Option<String>,
pub plugin_name: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginReadResponse {
pub plugin: PluginDetail,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginSkillReadParams {
pub remote_marketplace_name: String,
pub remote_plugin_id: String,
pub skill_name: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginSkillReadResponse {
pub contents: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginShareSaveParams {
pub plugin_path: AbsolutePathBuf,
#[ts(optional = nullable)]
pub remote_plugin_id: Option<String>,
#[ts(optional = nullable)]
pub discoverability: Option<PluginShareDiscoverability>,
#[ts(optional = nullable)]
pub share_targets: Option<Vec<PluginShareTarget>>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginShareSaveResponse {
pub remote_plugin_id: String,
pub share_url: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginShareUpdateTargetsParams {
pub remote_plugin_id: String,
pub share_targets: Vec<PluginShareTarget>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginShareUpdateTargetsResponse {
pub principals: Vec<PluginSharePrincipal>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginShareListParams {}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginShareListResponse {
pub data: Vec<PluginShareListItem>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginShareDeleteParams {
pub remote_plugin_id: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginShareDeleteResponse {}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginShareListItem {
pub plugin: PluginSummary,
pub share_url: String,
pub local_plugin_path: Option<AbsolutePathBuf>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[ts(export_to = "v2/")]
pub enum PluginShareDiscoverability {
#[serde(rename = "LISTED")]
#[ts(rename = "LISTED")]
Listed,
#[serde(rename = "UNLISTED")]
#[ts(rename = "UNLISTED")]
Unlisted,
#[serde(rename = "PRIVATE")]
#[ts(rename = "PRIVATE")]
Private,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[ts(export_to = "v2/")]
pub enum PluginSharePrincipalType {
#[serde(rename = "user")]
#[ts(rename = "user")]
User,
#[serde(rename = "group")]
#[ts(rename = "group")]
Group,
#[serde(rename = "workspace")]
#[ts(rename = "workspace")]
Workspace,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginShareTarget {
pub principal_type: PluginSharePrincipalType,
pub principal_id: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginSharePrincipal {
pub principal_type: PluginSharePrincipalType,
pub principal_id: String,
pub name: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "snake_case")]
#[ts(rename_all = "snake_case")]
#[ts(export_to = "v2/")]
pub enum SkillScope {
User,
Repo,
System,
Admin,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct SkillMetadata {
pub name: String,
pub description: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
/// Legacy short_description from SKILL.md. Prefer SKILL.json interface.short_description.
pub short_description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub interface: Option<SkillInterface>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub dependencies: Option<SkillDependencies>,
pub path: AbsolutePathBuf,
pub scope: SkillScope,
pub enabled: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct SkillInterface {
#[ts(optional)]
pub display_name: Option<String>,
#[ts(optional)]
pub short_description: Option<String>,
#[ts(optional)]
pub icon_small: Option<AbsolutePathBuf>,
#[ts(optional)]
pub icon_large: Option<AbsolutePathBuf>,
#[ts(optional)]
pub brand_color: Option<String>,
#[ts(optional)]
pub default_prompt: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct SkillDependencies {
pub tools: Vec<SkillToolDependency>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct SkillToolDependency {
#[serde(rename = "type")]
#[ts(rename = "type")]
pub r#type: String,
pub value: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub transport: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub command: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub url: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct SkillErrorInfo {
pub path: PathBuf,
pub message: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct SkillsListEntry {
pub cwd: PathBuf,
pub skills: Vec<SkillMetadata>,
pub errors: Vec<SkillErrorInfo>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct HooksListEntry {
pub cwd: PathBuf,
pub hooks: Vec<HookMetadata>,
pub warnings: Vec<String>,
pub errors: Vec<HookErrorInfo>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct HookMetadata {
pub key: String,
pub event_name: HookEventName,
pub handler_type: HookHandlerType,
pub matcher: Option<String>,
pub command: Option<String>,
pub timeout_sec: u64,
pub status_message: Option<String>,
pub source_path: AbsolutePathBuf,
pub source: HookSource,
pub plugin_id: Option<String>,
pub display_order: i64,
pub enabled: bool,
pub is_managed: bool,
pub current_hash: String,
pub trust_status: HookTrustStatus,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct HookErrorInfo {
pub path: PathBuf,
pub message: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginMarketplaceEntry {
pub name: String,
/// Local marketplace file path when the marketplace is backed by a local file.
/// Remote-only catalog marketplaces do not have a local path.
pub path: Option<AbsolutePathBuf>,
pub interface: Option<MarketplaceInterface>,
pub plugins: Vec<PluginSummary>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct MarketplaceInterface {
pub display_name: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[ts(export_to = "v2/")]
pub enum PluginInstallPolicy {
#[serde(rename = "NOT_AVAILABLE")]
#[ts(rename = "NOT_AVAILABLE")]
NotAvailable,
#[serde(rename = "AVAILABLE")]
#[ts(rename = "AVAILABLE")]
Available,
#[serde(rename = "INSTALLED_BY_DEFAULT")]
#[ts(rename = "INSTALLED_BY_DEFAULT")]
InstalledByDefault,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[ts(export_to = "v2/")]
pub enum PluginAuthPolicy {
#[serde(rename = "ON_INSTALL")]
#[ts(rename = "ON_INSTALL")]
OnInstall,
#[serde(rename = "ON_USE")]
#[ts(rename = "ON_USE")]
OnUse,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default, JsonSchema, TS)]
#[ts(export_to = "v2/")]
pub enum PluginAvailability {
/// Plugin-service currently sends `"ENABLED"` for available remote plugins.
/// Codex app-server exposes `"AVAILABLE"` in its API; the alias keeps
/// decoding compatible with that upstream response.
#[serde(rename = "AVAILABLE", alias = "ENABLED")]
#[ts(rename = "AVAILABLE")]
#[default]
Available,
#[serde(rename = "DISABLED_BY_ADMIN")]
#[ts(rename = "DISABLED_BY_ADMIN")]
DisabledByAdmin,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginSummary {
pub id: String,
pub name: String,
pub source: PluginSource,
pub installed: bool,
pub enabled: bool,
pub install_policy: PluginInstallPolicy,
pub auth_policy: PluginAuthPolicy,
/// Availability state for installing and using the plugin.
#[serde(default)]
pub availability: PluginAvailability,
pub interface: Option<PluginInterface>,
#[serde(default)]
pub keywords: Vec<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginDetail {
pub marketplace_name: String,
pub marketplace_path: Option<AbsolutePathBuf>,
pub summary: PluginSummary,
pub description: Option<String>,
pub skills: Vec<SkillSummary>,
pub apps: Vec<AppSummary>,
pub mcp_servers: Vec<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct SkillSummary {
pub name: String,
pub description: String,
pub short_description: Option<String>,
pub interface: Option<SkillInterface>,
pub path: Option<AbsolutePathBuf>,
pub enabled: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginInterface {
pub display_name: Option<String>,
pub short_description: Option<String>,
pub long_description: Option<String>,
pub developer_name: Option<String>,
pub category: Option<String>,
pub capabilities: Vec<String>,
pub website_url: Option<String>,
pub privacy_policy_url: Option<String>,
pub terms_of_service_url: Option<String>,
/// Starter prompts for the plugin. Capped at 3 entries with a maximum of
/// 128 characters per entry.
pub default_prompt: Option<Vec<String>>,
pub brand_color: Option<String>,
/// Local composer icon path, resolved from the installed plugin package.
pub composer_icon: Option<AbsolutePathBuf>,
/// Remote composer icon URL from the plugin catalog.
pub composer_icon_url: Option<String>,
/// Local logo path, resolved from the installed plugin package.
pub logo: Option<AbsolutePathBuf>,
/// Remote logo URL from the plugin catalog.
pub logo_url: Option<String>,
/// Local screenshot paths, resolved from the installed plugin package.
pub screenshots: Vec<AbsolutePathBuf>,
/// Remote screenshot URLs from the plugin catalog.
pub screenshot_urls: Vec<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "camelCase")]
#[ts(tag = "type")]
#[ts(export_to = "v2/")]
pub enum PluginSource {
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
Local { path: AbsolutePathBuf },
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
Git {
url: String,
path: Option<String>,
ref_name: Option<String>,
sha: Option<String>,
},
/// The plugin is available in the remote catalog. Download metadata is
/// kept server-side and is not exposed through the app-server API.
Remote,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct SkillsConfigWriteParams {
/// Path-based selector.
#[ts(optional = nullable)]
pub path: Option<AbsolutePathBuf>,
/// Name-based selector.
#[ts(optional = nullable)]
pub name: Option<String>,
pub enabled: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct SkillsConfigWriteResponse {
pub effective_enabled: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginInstallParams {
#[ts(optional = nullable)]
pub marketplace_path: Option<AbsolutePathBuf>,
#[ts(optional = nullable)]
pub remote_marketplace_name: Option<String>,
pub plugin_name: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginInstallResponse {
pub auth_policy: PluginAuthPolicy,
pub apps_needing_auth: Vec<AppSummary>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginUninstallParams {
pub plugin_id: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginUninstallResponse {}
impl From<CoreSkillMetadata> for SkillMetadata {
fn from(value: CoreSkillMetadata) -> Self {
Self {
name: value.name,
description: value.description,
short_description: value.short_description,
interface: value.interface.map(SkillInterface::from),
dependencies: value.dependencies.map(SkillDependencies::from),
path: value.path,
scope: value.scope.into(),
enabled: true,
}
}
}
impl From<CoreSkillInterface> for SkillInterface {
fn from(value: CoreSkillInterface) -> Self {
Self {
display_name: value.display_name,
short_description: value.short_description,
brand_color: value.brand_color,
default_prompt: value.default_prompt,
icon_small: value.icon_small,
icon_large: value.icon_large,
}
}
}
impl From<CoreSkillDependencies> for SkillDependencies {
fn from(value: CoreSkillDependencies) -> Self {
Self {
tools: value
.tools
.into_iter()
.map(SkillToolDependency::from)
.collect(),
}
}
}
impl From<CoreSkillToolDependency> for SkillToolDependency {
fn from(value: CoreSkillToolDependency) -> Self {
Self {
r#type: value.r#type,
value: value.value,
description: value.description,
transport: value.transport,
command: value.command,
url: value.url,
}
}
}
impl From<CoreSkillScope> for SkillScope {
fn from(value: CoreSkillScope) -> Self {
match value {
CoreSkillScope::User => Self::User,
CoreSkillScope::Repo => Self::Repo,
CoreSkillScope::System => Self::System,
CoreSkillScope::Admin => Self::Admin,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
/// Notification emitted when watched local skill files change.
///
/// Treat this as an invalidation signal and re-run `skills/list` with the
/// client's current parameters when refreshed skill metadata is needed.
pub struct SkillsChangedNotification {}

View File

@@ -0,0 +1,204 @@
use codex_utils_absolute_path::AbsolutePathBuf;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::collections::HashMap;
use ts_rs::TS;
/// PTY size in character cells for `process/spawn` PTY sessions.
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ProcessTerminalSize {
/// Terminal height in character cells.
pub rows: u16,
/// Terminal width in character cells.
pub cols: u16,
}
/// Spawn a standalone process (argv vector) without a Codex sandbox on the host
/// where the app server is running.
///
/// `process/spawn` returns after the process has started and the connection-scoped
/// `processHandle` has been registered. Process output and exit are reported via
/// `process/outputDelta` and `process/exited` notifications.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ProcessSpawnParams {
/// Command argv vector. Empty arrays are rejected.
pub command: Vec<String>,
/// Client-supplied, connection-scoped process handle.
///
/// Duplicate active handles are rejected on the same connection. The same
/// handle can be reused after the prior process exits.
pub process_handle: String,
/// Absolute working directory for the process.
pub cwd: AbsolutePathBuf,
/// Enable PTY mode.
///
/// This implies `streamStdin` and `streamStdoutStderr`.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub tty: bool,
/// Allow follow-up `process/writeStdin` requests to write stdin bytes.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub stream_stdin: bool,
/// Stream stdout/stderr via `process/outputDelta` notifications.
///
/// Streamed bytes are not duplicated into the `process/exited` notification.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub stream_stdout_stderr: bool,
/// Optional per-stream stdout/stderr capture cap in bytes.
///
/// When omitted, the server default applies. Set to `null` to disable the
/// cap.
#[serde(
default,
deserialize_with = "crate::protocol::serde_helpers::deserialize_double_option",
serialize_with = "crate::protocol::serde_helpers::serialize_double_option",
skip_serializing_if = "Option::is_none"
)]
#[ts(type = "number | null")]
#[ts(optional = nullable)]
pub output_bytes_cap: Option<Option<usize>>,
/// Optional timeout in milliseconds.
///
/// When omitted, the server default applies. Set to `null` to disable the
/// timeout.
#[serde(
default,
deserialize_with = "crate::protocol::serde_helpers::deserialize_double_option",
serialize_with = "crate::protocol::serde_helpers::serialize_double_option",
skip_serializing_if = "Option::is_none"
)]
#[ts(type = "number | null")]
#[ts(optional = nullable)]
pub timeout_ms: Option<Option<i64>>,
/// Optional environment overrides merged into the app-server process
/// environment.
///
/// Matching names override inherited values. Set a key to `null` to unset
/// an inherited variable.
#[ts(optional = nullable)]
pub env: Option<HashMap<String, Option<String>>>,
/// Optional initial PTY size in character cells. Only valid when `tty` is
/// true.
#[ts(optional = nullable)]
pub size: Option<ProcessTerminalSize>,
}
/// Successful response for `process/spawn`.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ProcessSpawnResponse {}
/// Write stdin bytes to a running `process/spawn` session, close stdin, or
/// both.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ProcessWriteStdinParams {
/// Client-supplied, connection-scoped `processHandle` from `process/spawn`.
pub process_handle: String,
/// Optional base64-encoded stdin bytes to write.
#[ts(optional = nullable)]
pub delta_base64: Option<String>,
/// Close stdin after writing `deltaBase64`, if present.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub close_stdin: bool,
}
/// Empty success response for `process/writeStdin`.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ProcessWriteStdinResponse {}
/// Terminate a running `process/spawn` session.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ProcessKillParams {
/// Client-supplied, connection-scoped `processHandle` from `process/spawn`.
pub process_handle: String,
}
/// Empty success response for `process/kill`.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ProcessKillResponse {}
/// Resize a running PTY-backed `process/spawn` session.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ProcessResizePtyParams {
/// Client-supplied, connection-scoped `processHandle` from `process/spawn`.
pub process_handle: String,
/// New PTY size in character cells.
pub size: ProcessTerminalSize,
}
/// Empty success response for `process/resizePty`.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ProcessResizePtyResponse {}
/// Stream label for `process/outputDelta` notifications.
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub enum ProcessOutputStream {
/// stdout stream. PTY mode multiplexes terminal output here.
Stdout,
/// stderr stream.
Stderr,
}
/// Base64-encoded output chunk emitted for a streaming `process/spawn` request.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ProcessOutputDeltaNotification {
/// Client-supplied, connection-scoped `processHandle` from `process/spawn`.
pub process_handle: String,
/// Output stream this chunk belongs to.
pub stream: ProcessOutputStream,
/// Base64-encoded output bytes.
pub delta_base64: String,
/// True on the final streamed chunk for this stream when output was
/// truncated by `outputBytesCap`.
pub cap_reached: bool,
}
/// Final process exit notification for `process/spawn`.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ProcessExitedNotification {
/// Client-supplied, connection-scoped `processHandle` from `process/spawn`.
pub process_handle: String,
/// Process exit code.
pub exit_code: i32,
/// Buffered stdout capture.
///
/// Empty when stdout was streamed via `process/outputDelta`.
pub stdout: String,
/// Whether stdout reached `outputBytesCap`.
///
/// In streaming mode, stdout is empty and cap state is also reported on the
/// final stdout `process/outputDelta` notification.
pub stdout_cap_reached: bool,
/// Buffered stderr capture.
///
/// Empty when stderr was streamed via `process/outputDelta`.
pub stderr: String,
/// Whether stderr reached `outputBytesCap`.
///
/// In streaming mode, stderr is empty and cap state is also reported on the
/// final stderr `process/outputDelta` notification.
pub stderr_cap_reached: bool,
}

View File

@@ -0,0 +1,241 @@
use codex_protocol::protocol::RealtimeAudioFrame as CoreRealtimeAudioFrame;
use codex_protocol::protocol::RealtimeConversationVersion;
use codex_protocol::protocol::RealtimeOutputModality;
use codex_protocol::protocol::RealtimeVoice;
use codex_protocol::protocol::RealtimeVoicesList;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use serde_json::Value as JsonValue;
use ts_rs::TS;
/// EXPERIMENTAL - thread realtime audio chunk.
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadRealtimeAudioChunk {
pub data: String,
pub sample_rate: u32,
pub num_channels: u16,
pub samples_per_channel: Option<u32>,
pub item_id: Option<String>,
}
impl From<CoreRealtimeAudioFrame> for ThreadRealtimeAudioChunk {
fn from(value: CoreRealtimeAudioFrame) -> Self {
let CoreRealtimeAudioFrame {
data,
sample_rate,
num_channels,
samples_per_channel,
item_id,
} = value;
Self {
data,
sample_rate,
num_channels,
samples_per_channel,
item_id,
}
}
}
impl From<ThreadRealtimeAudioChunk> for CoreRealtimeAudioFrame {
fn from(value: ThreadRealtimeAudioChunk) -> Self {
let ThreadRealtimeAudioChunk {
data,
sample_rate,
num_channels,
samples_per_channel,
item_id,
} = value;
Self {
data,
sample_rate,
num_channels,
samples_per_channel,
item_id,
}
}
}
/// EXPERIMENTAL - start a thread-scoped realtime session.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadRealtimeStartParams {
pub thread_id: String,
/// Selects text or audio output for the realtime session. Transport and voice stay
/// independent so clients can choose how they connect separately from what the model emits.
pub output_modality: RealtimeOutputModality,
#[serde(
default,
deserialize_with = "crate::protocol::serde_helpers::deserialize_double_option",
serialize_with = "crate::protocol::serde_helpers::serialize_double_option",
skip_serializing_if = "Option::is_none"
)]
#[ts(optional = nullable)]
pub prompt: Option<Option<String>>,
#[ts(optional = nullable)]
pub realtime_session_id: Option<String>,
#[ts(optional = nullable)]
pub transport: Option<ThreadRealtimeStartTransport>,
#[ts(optional = nullable)]
pub voice: Option<RealtimeVoice>,
}
/// EXPERIMENTAL - transport used by thread realtime.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "camelCase")]
#[ts(export_to = "v2/", tag = "type")]
pub enum ThreadRealtimeStartTransport {
Websocket,
Webrtc {
/// SDP offer generated by a WebRTC RTCPeerConnection after configuring audio and the
/// realtime events data channel.
sdp: String,
},
}
/// EXPERIMENTAL - response for starting thread realtime.
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadRealtimeStartResponse {}
/// EXPERIMENTAL - append audio input to thread realtime.
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadRealtimeAppendAudioParams {
pub thread_id: String,
pub audio: ThreadRealtimeAudioChunk,
}
/// EXPERIMENTAL - response for appending realtime audio input.
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadRealtimeAppendAudioResponse {}
/// EXPERIMENTAL - append text input to thread realtime.
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadRealtimeAppendTextParams {
pub thread_id: String,
pub text: String,
}
/// EXPERIMENTAL - response for appending realtime text input.
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadRealtimeAppendTextResponse {}
/// EXPERIMENTAL - stop thread realtime.
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadRealtimeStopParams {
pub thread_id: String,
}
/// EXPERIMENTAL - response for stopping thread realtime.
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadRealtimeStopResponse {}
/// EXPERIMENTAL - list voices supported by thread realtime.
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadRealtimeListVoicesParams {}
/// EXPERIMENTAL - response for listing supported realtime voices.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadRealtimeListVoicesResponse {
pub voices: RealtimeVoicesList,
}
/// EXPERIMENTAL - emitted when thread realtime startup is accepted.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadRealtimeStartedNotification {
pub thread_id: String,
pub realtime_session_id: Option<String>,
pub version: RealtimeConversationVersion,
}
/// EXPERIMENTAL - raw non-audio thread realtime item emitted by the backend.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadRealtimeItemAddedNotification {
pub thread_id: String,
pub item: JsonValue,
}
/// EXPERIMENTAL - flat transcript delta emitted whenever realtime
/// transcript text changes.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadRealtimeTranscriptDeltaNotification {
pub thread_id: String,
pub role: String,
/// Live transcript delta from the realtime event.
pub delta: String,
}
/// EXPERIMENTAL - final transcript text emitted when realtime completes
/// a transcript part.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadRealtimeTranscriptDoneNotification {
pub thread_id: String,
pub role: String,
/// Final complete text for the transcript part.
pub text: String,
}
/// EXPERIMENTAL - streamed output audio emitted by thread realtime.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadRealtimeOutputAudioDeltaNotification {
pub thread_id: String,
pub audio: ThreadRealtimeAudioChunk,
}
/// EXPERIMENTAL - emitted with the remote SDP for a WebRTC realtime session.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadRealtimeSdpNotification {
pub thread_id: String,
pub sdp: String,
}
/// EXPERIMENTAL - emitted when thread realtime encounters an error.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadRealtimeErrorNotification {
pub thread_id: String,
pub message: String,
}
/// EXPERIMENTAL - emitted when thread realtime transport closes.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadRealtimeClosedNotification {
pub thread_id: String,
pub reason: Option<String>,
}

View File

@@ -0,0 +1,65 @@
use super::Turn;
use super::shared::v2_enum_from_core;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use ts_rs::TS;
v2_enum_from_core!(
pub enum ReviewDelivery from codex_protocol::protocol::ReviewDelivery {
Inline, Detached
}
);
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ReviewStartParams {
pub thread_id: String,
pub target: ReviewTarget,
/// Where to run the review: inline (default) on the current thread or
/// detached on a new thread (returned in `reviewThreadId`).
#[serde(default)]
#[ts(optional = nullable)]
pub delivery: Option<ReviewDelivery>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ReviewStartResponse {
pub turn: Turn,
/// Identifies the thread where the review runs.
///
/// For inline reviews, this is the original thread id.
/// For detached reviews, this is the id of the new review thread.
pub review_thread_id: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "camelCase")]
#[ts(tag = "type", export_to = "v2/")]
pub enum ReviewTarget {
/// Review the working tree: staged, unstaged, and untracked files.
UncommittedChanges,
/// Review changes between the current branch and the given base branch.
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
BaseBranch { branch: String },
/// Review the changes introduced by a specific commit.
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
Commit {
sha: String,
/// Optional human-readable label (e.g., commit subject) for UIs.
title: Option<String>,
},
/// Arbitrary instructions, equivalent to the old free-form prompt.
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
Custom { instructions: String },
}

View File

@@ -0,0 +1,316 @@
use codex_experimental_api_macros::ExperimentalApi;
use codex_protocol::config_types::ApprovalsReviewer as CoreApprovalsReviewer;
use codex_protocol::config_types::SandboxMode as CoreSandboxMode;
use codex_protocol::protocol::AskForApproval as CoreAskForApproval;
use codex_protocol::protocol::CodexErrorInfo as CoreCodexErrorInfo;
use codex_protocol::protocol::GranularApprovalConfig as CoreGranularApprovalConfig;
use codex_protocol::protocol::NonSteerableTurnKind as CoreNonSteerableTurnKind;
use schemars::JsonSchema;
use schemars::r#gen::SchemaGenerator;
use schemars::schema::InstanceType;
use schemars::schema::Metadata;
use schemars::schema::Schema;
use schemars::schema::SchemaObject;
use serde::Deserialize;
use serde::Serialize;
use serde_json::Value as JsonValue;
use ts_rs::TS;
// Macro to declare a camelCased API v2 enum mirroring a core enum which
// tends to use either snake_case or kebab-case.
macro_rules! v2_enum_from_core {
(
$(#[$enum_meta:meta])*
pub enum $Name:ident from $Src:path {
$( $(#[$variant_meta:meta])* $Variant:ident ),+ $(,)?
}
) => {
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
$(#[$enum_meta])*
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub enum $Name {
$( $(#[$variant_meta])* $Variant ),+
}
impl $Name {
pub fn to_core(self) -> $Src {
match self { $( $Name::$Variant => <$Src>::$Variant ),+ }
}
}
impl From<$Src> for $Name {
fn from(value: $Src) -> Self {
match value { $( <$Src>::$Variant => $Name::$Variant ),+ }
}
}
};
}
pub(super) use v2_enum_from_core;
pub(super) const fn default_enabled() -> bool {
true
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub enum NonSteerableTurnKind {
Review,
Compact,
}
/// This translation layer make sure that we expose codex error code in camel case.
///
/// When an upstream HTTP status is available (for example, from the Responses API or a provider),
/// it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub enum CodexErrorInfo {
ContextWindowExceeded,
UsageLimitExceeded,
ServerOverloaded,
CyberPolicy,
HttpConnectionFailed {
#[serde(rename = "httpStatusCode")]
#[ts(rename = "httpStatusCode")]
http_status_code: Option<u16>,
},
/// Failed to connect to the response SSE stream.
ResponseStreamConnectionFailed {
#[serde(rename = "httpStatusCode")]
#[ts(rename = "httpStatusCode")]
http_status_code: Option<u16>,
},
InternalServerError,
Unauthorized,
BadRequest,
ThreadRollbackFailed,
SandboxError,
/// The response SSE stream disconnected in the middle of a turn before completion.
ResponseStreamDisconnected {
#[serde(rename = "httpStatusCode")]
#[ts(rename = "httpStatusCode")]
http_status_code: Option<u16>,
},
/// Reached the retry limit for responses.
ResponseTooManyFailedAttempts {
#[serde(rename = "httpStatusCode")]
#[ts(rename = "httpStatusCode")]
http_status_code: Option<u16>,
},
/// Returned when `turn/start` or `turn/steer` is submitted while the current active turn
/// cannot accept same-turn steering, for example `/review` or manual `/compact`.
ActiveTurnNotSteerable {
#[serde(rename = "turnKind")]
#[ts(rename = "turnKind")]
turn_kind: NonSteerableTurnKind,
},
Other,
}
impl From<CoreCodexErrorInfo> for CodexErrorInfo {
fn from(value: CoreCodexErrorInfo) -> Self {
match value {
CoreCodexErrorInfo::ContextWindowExceeded => CodexErrorInfo::ContextWindowExceeded,
CoreCodexErrorInfo::UsageLimitExceeded => CodexErrorInfo::UsageLimitExceeded,
CoreCodexErrorInfo::ServerOverloaded => CodexErrorInfo::ServerOverloaded,
CoreCodexErrorInfo::CyberPolicy => CodexErrorInfo::CyberPolicy,
CoreCodexErrorInfo::HttpConnectionFailed { http_status_code } => {
CodexErrorInfo::HttpConnectionFailed { http_status_code }
}
CoreCodexErrorInfo::ResponseStreamConnectionFailed { http_status_code } => {
CodexErrorInfo::ResponseStreamConnectionFailed { http_status_code }
}
CoreCodexErrorInfo::InternalServerError => CodexErrorInfo::InternalServerError,
CoreCodexErrorInfo::Unauthorized => CodexErrorInfo::Unauthorized,
CoreCodexErrorInfo::BadRequest => CodexErrorInfo::BadRequest,
CoreCodexErrorInfo::ThreadRollbackFailed => CodexErrorInfo::ThreadRollbackFailed,
CoreCodexErrorInfo::SandboxError => CodexErrorInfo::SandboxError,
CoreCodexErrorInfo::ResponseStreamDisconnected { http_status_code } => {
CodexErrorInfo::ResponseStreamDisconnected { http_status_code }
}
CoreCodexErrorInfo::ResponseTooManyFailedAttempts { http_status_code } => {
CodexErrorInfo::ResponseTooManyFailedAttempts { http_status_code }
}
CoreCodexErrorInfo::ActiveTurnNotSteerable { turn_kind } => {
CodexErrorInfo::ActiveTurnNotSteerable {
turn_kind: turn_kind.into(),
}
}
CoreCodexErrorInfo::Other => CodexErrorInfo::Other,
}
}
}
impl From<CoreNonSteerableTurnKind> for NonSteerableTurnKind {
fn from(value: CoreNonSteerableTurnKind) -> Self {
match value {
CoreNonSteerableTurnKind::Review => Self::Review,
CoreNonSteerableTurnKind::Compact => Self::Compact,
}
}
}
#[derive(
Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS, ExperimentalApi,
)]
#[serde(rename_all = "kebab-case")]
#[ts(rename_all = "kebab-case", export_to = "v2/")]
pub enum AskForApproval {
#[serde(rename = "untrusted")]
#[ts(rename = "untrusted")]
UnlessTrusted,
OnFailure,
OnRequest,
#[experimental("askForApproval.granular")]
Granular {
sandbox_approval: bool,
rules: bool,
#[serde(default)]
skill_approval: bool,
#[serde(default)]
request_permissions: bool,
mcp_elicitations: bool,
},
Never,
}
impl AskForApproval {
pub fn to_core(self) -> CoreAskForApproval {
match self {
AskForApproval::UnlessTrusted => CoreAskForApproval::UnlessTrusted,
AskForApproval::OnFailure => CoreAskForApproval::OnFailure,
AskForApproval::OnRequest => CoreAskForApproval::OnRequest,
AskForApproval::Granular {
sandbox_approval,
rules,
skill_approval,
request_permissions,
mcp_elicitations,
} => CoreAskForApproval::Granular(CoreGranularApprovalConfig {
sandbox_approval,
rules,
skill_approval,
request_permissions,
mcp_elicitations,
}),
AskForApproval::Never => CoreAskForApproval::Never,
}
}
}
impl From<CoreAskForApproval> for AskForApproval {
fn from(value: CoreAskForApproval) -> Self {
match value {
CoreAskForApproval::UnlessTrusted => AskForApproval::UnlessTrusted,
CoreAskForApproval::OnFailure => AskForApproval::OnFailure,
CoreAskForApproval::OnRequest => AskForApproval::OnRequest,
CoreAskForApproval::Granular(granular_config) => AskForApproval::Granular {
sandbox_approval: granular_config.sandbox_approval,
rules: granular_config.rules,
skill_approval: granular_config.skill_approval,
request_permissions: granular_config.request_permissions,
mcp_elicitations: granular_config.mcp_elicitations,
},
CoreAskForApproval::Never => AskForApproval::Never,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, TS)]
#[ts(
type = r#""user" | "auto_review" | "guardian_subagent""#,
export_to = "v2/"
)]
/// Configures who approval requests are routed to for review. Examples
/// include sandbox escapes, blocked network access, MCP approval prompts, and
/// ARC escalations. Defaults to `user`. `auto_review` uses a carefully
/// prompted subagent to gather relevant context and apply a risk-based
/// decision framework before approving or denying the request.
pub enum ApprovalsReviewer {
#[serde(rename = "user")]
User,
#[serde(rename = "guardian_subagent", alias = "auto_review")]
AutoReview,
}
impl JsonSchema for ApprovalsReviewer {
fn schema_name() -> String {
"ApprovalsReviewer".to_string()
}
fn json_schema(_generator: &mut SchemaGenerator) -> Schema {
string_enum_schema_with_description(
&["user", "auto_review", "guardian_subagent"],
"Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.",
)
}
}
fn string_enum_schema_with_description(values: &[&str], description: &str) -> Schema {
let mut schema = SchemaObject {
instance_type: Some(InstanceType::String.into()),
metadata: Some(Box::new(Metadata {
description: Some(description.to_string()),
..Default::default()
})),
..Default::default()
};
schema.enum_values = Some(
values
.iter()
.map(|value| JsonValue::String((*value).to_string()))
.collect(),
);
Schema::Object(schema)
}
impl ApprovalsReviewer {
pub fn to_core(self) -> CoreApprovalsReviewer {
match self {
ApprovalsReviewer::User => CoreApprovalsReviewer::User,
ApprovalsReviewer::AutoReview => CoreApprovalsReviewer::AutoReview,
}
}
}
impl From<CoreApprovalsReviewer> for ApprovalsReviewer {
fn from(value: CoreApprovalsReviewer) -> Self {
match value {
CoreApprovalsReviewer::User => ApprovalsReviewer::User,
CoreApprovalsReviewer::AutoReview => ApprovalsReviewer::AutoReview,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "kebab-case")]
#[ts(rename_all = "kebab-case", export_to = "v2/")]
pub enum SandboxMode {
ReadOnly,
WorkspaceWrite,
DangerFullAccess,
}
impl SandboxMode {
pub fn to_core(self) -> CoreSandboxMode {
match self {
SandboxMode::ReadOnly => CoreSandboxMode::ReadOnly,
SandboxMode::WorkspaceWrite => CoreSandboxMode::WorkspaceWrite,
SandboxMode::DangerFullAccess => CoreSandboxMode::DangerFullAccess,
}
}
}
impl From<CoreSandboxMode> for SandboxMode {
fn from(value: CoreSandboxMode) -> Self {
match value {
CoreSandboxMode::ReadOnly => SandboxMode::ReadOnly,
CoreSandboxMode::WorkspaceWrite => SandboxMode::WorkspaceWrite,
CoreSandboxMode::DangerFullAccess => SandboxMode::DangerFullAccess,
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,194 @@
use super::CodexErrorInfo;
use super::ThreadItem;
use super::ThreadStatus;
use super::TurnStatus;
use codex_protocol::protocol::SessionSource as CoreSessionSource;
use codex_protocol::protocol::SubAgentSource as CoreSubAgentSource;
use codex_protocol::protocol::ThreadSource as CoreThreadSource;
use codex_utils_absolute_path::AbsolutePathBuf;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::path::PathBuf;
use thiserror::Error;
use ts_rs::TS;
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase", export_to = "v2/")]
#[derive(Default)]
pub enum SessionSource {
Cli,
#[serde(rename = "vscode")]
#[ts(rename = "vscode")]
#[default]
VsCode,
Exec,
AppServer,
Custom(String),
SubAgent(CoreSubAgentSource),
#[serde(other)]
Unknown,
}
impl From<CoreSessionSource> for SessionSource {
fn from(value: CoreSessionSource) -> Self {
match value {
CoreSessionSource::Cli => SessionSource::Cli,
CoreSessionSource::VSCode => SessionSource::VsCode,
CoreSessionSource::Exec => SessionSource::Exec,
CoreSessionSource::Mcp => SessionSource::AppServer,
CoreSessionSource::Custom(source) => SessionSource::Custom(source),
// We do not want to render those at the app-server level.
CoreSessionSource::Internal(_) => SessionSource::Unknown,
CoreSessionSource::SubAgent(sub) => SessionSource::SubAgent(sub),
CoreSessionSource::Unknown => SessionSource::Unknown,
}
}
}
impl From<SessionSource> for CoreSessionSource {
fn from(value: SessionSource) -> Self {
match value {
SessionSource::Cli => CoreSessionSource::Cli,
SessionSource::VsCode => CoreSessionSource::VSCode,
SessionSource::Exec => CoreSessionSource::Exec,
SessionSource::AppServer => CoreSessionSource::Mcp,
SessionSource::Custom(source) => CoreSessionSource::Custom(source),
SessionSource::SubAgent(sub) => CoreSessionSource::SubAgent(sub),
SessionSource::Unknown => CoreSessionSource::Unknown,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "snake_case")]
#[ts(rename_all = "snake_case", export_to = "v2/")]
pub enum ThreadSource {
User,
Subagent,
MemoryConsolidation,
}
impl From<CoreThreadSource> for ThreadSource {
fn from(value: CoreThreadSource) -> Self {
match value {
CoreThreadSource::User => ThreadSource::User,
CoreThreadSource::Subagent => ThreadSource::Subagent,
CoreThreadSource::MemoryConsolidation => ThreadSource::MemoryConsolidation,
}
}
}
impl From<ThreadSource> for CoreThreadSource {
fn from(value: ThreadSource) -> Self {
match value {
ThreadSource::User => CoreThreadSource::User,
ThreadSource::Subagent => CoreThreadSource::Subagent,
ThreadSource::MemoryConsolidation => CoreThreadSource::MemoryConsolidation,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct GitInfo {
pub sha: Option<String>,
pub branch: Option<String>,
pub origin_url: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct Thread {
pub id: String,
/// Source thread id when this thread was created by forking another thread.
pub forked_from_id: Option<String>,
/// Usually the first user message in the thread, if available.
pub preview: String,
/// Whether the thread is ephemeral and should not be materialized on disk.
pub ephemeral: bool,
/// Model provider used for this thread (for example, 'openai').
pub model_provider: String,
/// Unix timestamp (in seconds) when the thread was created.
#[ts(type = "number")]
pub created_at: i64,
/// Unix timestamp (in seconds) when the thread was last updated.
#[ts(type = "number")]
pub updated_at: i64,
/// Current runtime status for the thread.
pub status: ThreadStatus,
/// [UNSTABLE] Path to the thread on disk.
pub path: Option<PathBuf>,
/// Working directory captured for the thread.
pub cwd: AbsolutePathBuf,
/// Version of the CLI that created the thread.
pub cli_version: String,
/// Origin of the thread (CLI, VSCode, codex exec, codex app-server, etc.).
pub source: SessionSource,
/// Optional analytics source classification for this thread.
pub thread_source: Option<ThreadSource>,
/// Optional random unique nickname assigned to an AgentControl-spawned sub-agent.
pub agent_nickname: Option<String>,
/// Optional role (agent_role) assigned to an AgentControl-spawned sub-agent.
pub agent_role: Option<String>,
/// Optional Git metadata captured when the thread was created.
pub git_info: Option<GitInfo>,
/// Optional user-facing thread title.
pub name: Option<String>,
/// Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read`
/// (when `includeTurns` is true) responses.
/// For all other responses and notifications returning a Thread,
/// the turns field will be an empty list.
pub turns: Vec<Turn>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct Turn {
pub id: String,
/// Thread items currently included in this turn payload.
pub items: Vec<ThreadItem>,
/// Describes how much of `items` has been loaded for this turn.
#[serde(default)]
pub items_view: TurnItemsView,
pub status: TurnStatus,
/// Only populated when the Turn's status is failed.
pub error: Option<TurnError>,
/// Unix timestamp (in seconds) when the turn started.
#[ts(type = "number | null")]
pub started_at: Option<i64>,
/// Unix timestamp (in seconds) when the turn completed.
#[ts(type = "number | null")]
pub completed_at: Option<i64>,
/// Duration between turn start and completion in milliseconds, if known.
#[ts(type = "number | null")]
pub duration_ms: Option<i64>,
}
#[derive(Default, Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub enum TurnItemsView {
/// `items` was not loaded for this turn. The field is intentionally empty.
NotLoaded,
/// `items` contains only a display summary for this turn.
Summary,
/// `items` contains every ThreadItem available from persisted app-server history for this turn.
#[default]
Full,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, Error)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
#[error("{message}")]
pub struct TurnError {
pub message: String,
pub codex_error_info: Option<CodexErrorInfo>,
#[serde(default)]
pub additional_details: Option<String>,
}

View File

@@ -0,0 +1,390 @@
use super::ApprovalsReviewer;
use super::AskForApproval;
use super::PermissionProfileSelectionParams;
use super::SandboxPolicy;
use super::Turn;
use codex_experimental_api_macros::ExperimentalApi;
use codex_protocol::config_types::CollaborationMode;
use codex_protocol::config_types::Personality;
use codex_protocol::config_types::ReasoningSummary;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::openai_models::ReasoningEffort;
use codex_protocol::plan_tool::PlanItemArg as CorePlanItemArg;
use codex_protocol::plan_tool::StepStatus as CorePlanStepStatus;
use codex_protocol::user_input::ByteRange as CoreByteRange;
use codex_protocol::user_input::TextElement as CoreTextElement;
use codex_protocol::user_input::UserInput as CoreUserInput;
use codex_utils_absolute_path::AbsolutePathBuf;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use serde_json::Value as JsonValue;
use std::collections::HashMap;
use std::path::PathBuf;
use ts_rs::TS;
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub enum TurnStatus {
Completed,
Interrupted,
Failed,
InProgress,
}
// Turn APIs
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct TurnEnvironmentParams {
pub environment_id: String,
pub cwd: AbsolutePathBuf,
}
#[derive(
Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS, ExperimentalApi,
)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct TurnStartParams {
pub thread_id: String,
pub input: Vec<UserInput>,
/// Optional turn-scoped Responses API client metadata.
#[experimental("turn/start.responsesapiClientMetadata")]
#[ts(optional = nullable)]
pub responsesapi_client_metadata: Option<HashMap<String, String>>,
/// Optional turn-scoped environments.
///
/// Omitted uses the thread sticky environments. Empty disables
/// environment access for this turn. Non-empty selects the first
/// environment as the current turn environment for this turn.
#[experimental("turn/start.environments")]
#[ts(optional = nullable)]
pub environments: Option<Vec<TurnEnvironmentParams>>,
/// Override the working directory for this turn and subsequent turns.
#[ts(optional = nullable)]
pub cwd: Option<PathBuf>,
/// Override the approval policy for this turn and subsequent turns.
#[experimental(nested)]
#[ts(optional = nullable)]
pub approval_policy: Option<AskForApproval>,
/// Override where approval requests are routed for review on this turn and
/// subsequent turns.
#[ts(optional = nullable)]
pub approvals_reviewer: Option<ApprovalsReviewer>,
/// Override the sandbox policy for this turn and subsequent turns.
#[ts(optional = nullable)]
pub sandbox_policy: Option<SandboxPolicy>,
/// Select a named permissions profile for this turn and subsequent turns.
/// Cannot be combined with `sandboxPolicy`. Use bounded `modifications`
/// for supported turn adjustments instead of replacing the full
/// permissions profile.
#[experimental("turn/start.permissions")]
#[ts(optional = nullable)]
pub permissions: Option<PermissionProfileSelectionParams>,
/// 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 = "crate::protocol::serde_helpers::deserialize_double_option",
serialize_with = "crate::protocol::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>,
/// Override the reasoning summary for this turn and subsequent turns.
#[ts(optional = nullable)]
pub summary: Option<ReasoningSummary>,
/// Override the personality for this turn and subsequent turns.
#[ts(optional = nullable)]
pub personality: Option<Personality>,
/// Optional JSON Schema used to constrain the final assistant message for
/// this turn.
#[ts(optional = nullable)]
pub output_schema: Option<JsonValue>,
/// EXPERIMENTAL - Set a pre-set collaboration mode.
/// Takes precedence over model, reasoning_effort, and developer instructions if set.
///
/// For `collaboration_mode.settings.developer_instructions`, `null` means
/// "use the built-in instructions for the selected mode".
#[experimental("turn/start.collaborationMode")]
#[ts(optional = nullable)]
pub collaboration_mode: Option<CollaborationMode>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct TurnStartResponse {
pub turn: Turn,
}
#[derive(
Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS, ExperimentalApi,
)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct TurnSteerParams {
pub thread_id: String,
pub input: Vec<UserInput>,
/// Optional turn-scoped Responses API client metadata.
#[experimental("turn/steer.responsesapiClientMetadata")]
#[ts(optional = nullable)]
pub responsesapi_client_metadata: Option<HashMap<String, String>>,
/// Required active turn id precondition. The request fails when it does not
/// match the currently active turn.
pub expected_turn_id: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct TurnSteerResponse {
pub turn_id: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct TurnInterruptParams {
pub thread_id: String,
pub turn_id: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct TurnInterruptResponse {}
// User input types
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ByteRange {
pub start: usize,
pub end: usize,
}
impl From<CoreByteRange> for ByteRange {
fn from(value: CoreByteRange) -> Self {
Self {
start: value.start,
end: value.end,
}
}
}
impl From<ByteRange> for CoreByteRange {
fn from(value: ByteRange) -> Self {
Self {
start: value.start,
end: value.end,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct TextElement {
/// Byte range in the parent `text` buffer that this element occupies.
pub byte_range: ByteRange,
/// Optional human-readable placeholder for the element, displayed in the UI.
placeholder: Option<String>,
}
impl TextElement {
pub fn new(byte_range: ByteRange, placeholder: Option<String>) -> Self {
Self {
byte_range,
placeholder,
}
}
pub fn set_placeholder(&mut self, placeholder: Option<String>) {
self.placeholder = placeholder;
}
pub fn placeholder(&self) -> Option<&str> {
self.placeholder.as_deref()
}
}
impl From<CoreTextElement> for TextElement {
fn from(value: CoreTextElement) -> Self {
Self::new(
value.byte_range.into(),
value._placeholder_for_conversion_only().map(str::to_string),
)
}
}
impl From<TextElement> for CoreTextElement {
fn from(value: TextElement) -> Self {
Self::new(value.byte_range.into(), value.placeholder)
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "camelCase")]
#[ts(tag = "type")]
#[ts(export_to = "v2/")]
pub enum UserInput {
Text {
text: String,
/// UI-defined spans within `text` used to render or persist special elements.
#[serde(default)]
text_elements: Vec<TextElement>,
},
Image {
url: String,
},
LocalImage {
path: PathBuf,
},
Skill {
name: String,
path: PathBuf,
},
Mention {
name: String,
path: String,
},
}
impl UserInput {
pub fn into_core(self) -> CoreUserInput {
match self {
UserInput::Text {
text,
text_elements,
} => CoreUserInput::Text {
text,
text_elements: text_elements.into_iter().map(Into::into).collect(),
},
UserInput::Image { url } => CoreUserInput::Image { image_url: url },
UserInput::LocalImage { path } => CoreUserInput::LocalImage { path },
UserInput::Skill { name, path } => CoreUserInput::Skill { name, path },
UserInput::Mention { name, path } => CoreUserInput::Mention { name, path },
}
}
}
impl From<CoreUserInput> for UserInput {
fn from(value: CoreUserInput) -> Self {
match value {
CoreUserInput::Text {
text,
text_elements,
} => UserInput::Text {
text,
text_elements: text_elements.into_iter().map(Into::into).collect(),
},
CoreUserInput::Image { image_url } => UserInput::Image { url: image_url },
CoreUserInput::LocalImage { path } => UserInput::LocalImage { path },
CoreUserInput::Skill { name, path } => UserInput::Skill { name, path },
CoreUserInput::Mention { name, path } => UserInput::Mention { name, path },
_ => unreachable!("unsupported user input variant"),
}
}
}
impl UserInput {
pub fn text_char_count(&self) -> usize {
match self {
UserInput::Text { text, .. } => text.chars().count(),
UserInput::Image { .. }
| UserInput::LocalImage { .. }
| UserInput::Skill { .. }
| UserInput::Mention { .. } => 0,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct TurnStartedNotification {
pub thread_id: String,
pub turn: Turn,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct Usage {
pub input_tokens: i32,
pub cached_input_tokens: i32,
pub output_tokens: i32,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct TurnCompletedNotification {
pub thread_id: String,
pub turn: Turn,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
/// Notification that the turn-level unified diff has changed.
/// Contains the latest aggregated diff across all file changes in the turn.
pub struct TurnDiffUpdatedNotification {
pub thread_id: String,
pub turn_id: String,
pub diff: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct TurnPlanUpdatedNotification {
pub thread_id: String,
pub turn_id: String,
pub explanation: Option<String>,
pub plan: Vec<TurnPlanStep>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct TurnPlanStep {
pub step: String,
pub status: TurnPlanStepStatus,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub enum TurnPlanStepStatus {
Pending,
InProgress,
Completed,
}
impl From<CorePlanItemArg> for TurnPlanStep {
fn from(value: CorePlanItemArg) -> Self {
Self {
step: value.step,
status: value.status.into(),
}
}
}
impl From<CorePlanStepStatus> for TurnPlanStepStatus {
fn from(value: CorePlanStepStatus) -> Self {
match value {
CorePlanStepStatus::Pending => Self::Pending,
CorePlanStepStatus::InProgress => Self::InProgress,
CorePlanStepStatus::Completed => Self::Completed,
}
}
}

View File

@@ -0,0 +1,63 @@
use codex_utils_absolute_path::AbsolutePathBuf;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use ts_rs::TS;
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct WindowsWorldWritableWarningNotification {
pub sample_paths: Vec<String>,
pub extra_count: usize,
pub failed_scan: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub enum WindowsSandboxSetupMode {
Elevated,
Unelevated,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub enum WindowsSandboxReadiness {
Ready,
NotConfigured,
UpdateRequired,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct WindowsSandboxSetupStartParams {
pub mode: WindowsSandboxSetupMode,
#[ts(optional = nullable)]
pub cwd: Option<AbsolutePathBuf>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct WindowsSandboxSetupStartResponse {
pub started: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct WindowsSandboxReadinessResponse {
pub status: WindowsSandboxReadiness,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct WindowsSandboxSetupCompletedNotification {
pub mode: WindowsSandboxSetupMode,
pub success: bool,
pub error: Option<String>,
}

View File

@@ -14,5 +14,8 @@ codex_rust_crate(
"app-server-all-test": 16,
"app-server-unit-tests": 8,
},
extra_binaries = [
"//codex-rs/bwrap:bwrap",
],
test_tags = ["no-sandbox"],
)

View File

@@ -303,11 +303,11 @@ Example:
{ "id": 12, "result": { "thread": { "id": "thr_123", "turns": [], } } }
```
To branch from a stored session, call `thread/fork` with the `thread.id`. This creates a new thread id and emits a `thread/started` notification for it. When the source history includes persisted token usage, the server also emits `thread/tokenUsage/updated` for the new thread immediately after the response. If the source thread is actively running, the fork snapshots it as if the current turn had been interrupted first. Pass `ephemeral: true` when the fork should stay in-memory only:
To branch from a stored session, call `thread/fork` with the `thread.id`. This creates a new thread id and emits a `thread/started` notification for it. The response includes the forked thread's `sessionId`, so clients do not need to infer it from the new thread id. When the source history includes persisted token usage, the server also emits `thread/tokenUsage/updated` for the new thread immediately after the response. If the source thread is actively running, the fork snapshots it as if the current turn had been interrupted first. Pass `ephemeral: true` when the fork should stay in-memory only:
```json
{ "method": "thread/fork", "id": 12, "params": { "threadId": "thr_123", "ephemeral": true } }
{ "id": 12, "result": { "thread": { "id": "thr_456", } } }
{ "id": 12, "result": { "sessionId": "thr_456", "thread": { "id": "thr_456", } } }
{ "method": "thread/started", "params": { "thread": { } } }
```
@@ -1393,6 +1393,12 @@ If the session approval policy uses `Granular` with `request_permissions: false`
`dynamicTools` on `thread/start` and the corresponding `item/tool/call` request/response flow are experimental APIs. To enable them, set `initialize.params.capabilities.experimentalApi = true`.
Dynamic tool identifiers follow the same constraints as Responses function tools:
- `name` must match `^[a-zA-Z0-9_-]+$` and be between 1 and 128 characters.
- `namespace`, when present, must match `^[a-zA-Z0-9_-]+$` and be between 1 and 64 characters.
- `namespace` must not collide with reserved Responses runtime namespaces such as `functions`, `multi_tool_use`, `file_search`, `web`, `browser`, `image_gen`, `computer`, `container`, `terminal`, `python`, `python_user_visible`, `api_tool`, `tool_search`, or `submodel_delegator`.
Each dynamic tool may set `deferLoading`. When omitted, it defaults to `false`. Set it to `true` to keep the tool registered and callable by runtime features such as `code_mode`, while excluding it from the model-facing tool list sent on ordinary turns. When `tool_search` is available, deferred dynamic tools are searchable and can be exposed by a matching search result.
When a dynamic tool is invoked during a turn, the server sends an `item/tool/call` JSON-RPC request to the client:

View File

@@ -53,7 +53,6 @@ use codex_app_server_protocol::ServerRequestPayload;
use codex_app_server_protocol::SkillsChangedNotification;
use codex_app_server_protocol::ThreadGoalUpdatedNotification;
use codex_app_server_protocol::ThreadItem;
use codex_app_server_protocol::ThreadNameUpdatedNotification;
use codex_app_server_protocol::ThreadRealtimeClosedNotification;
use codex_app_server_protocol::ThreadRealtimeErrorNotification;
use codex_app_server_protocol::ThreadRealtimeItemAddedNotification;
@@ -1207,17 +1206,6 @@ pub(crate) async fn apply_bespoke_event_handling(
outgoing.send_response(request_id, response).await;
}
}
EventMsg::ThreadNameUpdated(thread_name_event) => {
let notification = ThreadNameUpdatedNotification {
thread_id: thread_name_event.thread_id.to_string(),
thread_name: thread_name_event.thread_name,
};
outgoing
.send_global_server_notification(ServerNotification::ThreadNameUpdated(
notification,
))
.await;
}
EventMsg::ThreadGoalUpdated(thread_goal_event) => {
let notification = ThreadGoalUpdatedNotification {
thread_id: thread_goal_event.thread_id.to_string(),
@@ -2194,6 +2182,7 @@ mod tests {
cwd: test_path_buf("/tmp").abs().into(),
cli_version: "0.0.0".to_string(),
source: SessionSource::Cli,
thread_source: None,
agent_nickname: None,
agent_role: None,
agent_path: None,
@@ -2629,7 +2618,8 @@ mod tests {
config.model_provider.clone(),
config.codex_home.to_path_buf(),
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
),
)
.await,
);
let codex_core::NewThread {
thread_id: conversation_id,
@@ -3214,7 +3204,8 @@ mod tests {
config.model_provider.clone(),
config.codex_home.to_path_buf(),
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
),
)
.await,
);
let codex_core::NewThread {
thread_id: conversation_id,

View File

@@ -140,6 +140,21 @@ impl ConfigManager {
.await
}
pub(crate) async fn load_latest_config_for_thread(
&self,
thread_config: &Config,
) -> std::io::Result<Config> {
let refreshed_config = self
.load_latest_config(Some(thread_config.cwd.to_path_buf()))
.await?;
let mut config = thread_config
.rebuild_preserving_session_layers(&refreshed_config)
.await?;
self.apply_runtime_feature_enablement(&mut config);
self.apply_arg0_paths(&mut config);
Ok(config)
}
pub(crate) async fn load_default_config(&self) -> std::io::Result<Config> {
let mut config = Config::load_default_with_cli_overrides_for_codex_home(
self.codex_home.clone(),

View File

@@ -82,11 +82,12 @@ use codex_config::CloudRequirementsLoader;
use codex_config::LoaderOverrides;
use codex_config::ThreadConfigLoader;
use codex_core::config::Config;
use codex_core::init_state_db_from_config;
use codex_exec_server::EnvironmentManager;
use codex_feedback::CodexFeedback;
use codex_login::AuthManager;
use codex_protocol::protocol::SessionSource;
pub use codex_rollout::StateDbHandle;
use codex_rollout::state_db::StateDbHandle;
pub use codex_state::log_db::LogDbLayer;
use tokio::sync::mpsc;
use tokio::sync::oneshot;
@@ -127,7 +128,7 @@ pub struct InProcessStartArgs {
pub feedback: CodexFeedback,
/// SQLite tracing layer used to flush recently emitted logs before feedback upload.
pub log_db: Option<LogDbLayer>,
/// Process-wide SQLite state handle shared with embedded app-server consumers.
/// Optional state DB handle to use for the in-process runtime.
pub state_db: Option<StateDbHandle>,
/// Environment manager used by core execution and filesystem operations.
pub environment_manager: Arc<EnvironmentManager>,
@@ -344,7 +345,7 @@ impl InProcessClientHandle {
/// the runtime is shut down and an `InvalidData` error is returned.
pub async fn start(args: InProcessStartArgs) -> IoResult<InProcessClientHandle> {
let initialize = args.initialize.clone();
let client = start_uninitialized(args);
let client = start_uninitialized(args).await;
let initialize_response = client
.request(ClientRequest::Initialize {
@@ -364,8 +365,12 @@ pub async fn start(args: InProcessStartArgs) -> IoResult<InProcessClientHandle>
Ok(client)
}
fn start_uninitialized(args: InProcessStartArgs) -> InProcessClientHandle {
async fn start_uninitialized(args: InProcessStartArgs) -> InProcessClientHandle {
let channel_capacity = args.channel_capacity.max(1);
let state_db = match args.state_db.clone() {
Some(state_db) => Some(state_db),
None => init_state_db_from_config(args.config.as_ref()).await,
};
let (client_tx, mut client_rx) = mpsc::channel::<InProcessClientMessage>(channel_capacity);
let (event_tx, event_rx) = mpsc::channel::<InProcessServerEvent>(channel_capacity);
@@ -414,6 +419,12 @@ fn start_uninitialized(args: InProcessStartArgs) -> InProcessClientHandle {
);
let (processor_tx, mut processor_rx) = mpsc::channel::<ProcessorCommand>(channel_capacity);
let mut processor_handle = tokio::spawn(async move {
let Some(state_db) = state_db else {
warn!(
"in-process app-server state db initialization failed; shutting down processor task"
);
return;
};
let processor = Arc::new(MessageProcessor::new(MessageProcessorArgs {
outgoing: Arc::clone(&processor_outgoing),
analytics_events_client,
@@ -423,7 +434,7 @@ fn start_uninitialized(args: InProcessStartArgs) -> InProcessClientHandle {
environment_manager: args.environment_manager,
feedback: args.feedback,
log_db: args.log_db,
state_db: args.state_db,
state_db,
config_warnings: args.config_warnings,
session_source: args.session_source,
auth_manager,
@@ -761,7 +772,7 @@ mod tests {
) -> InProcessClientHandle {
let codex_home = TempDir::new().expect("temp dir");
let config = Arc::new(build_test_config(codex_home.path()).await);
let state_db = codex_rollout::state_db::try_init(config.as_ref())
let state_db = init_state_db_from_config(config.as_ref())
.await
.expect("state db should initialize for in-process test");
let args = InProcessStartArgs {
@@ -819,7 +830,7 @@ mod tests {
}
#[tokio::test]
async fn in_process_allows_device_key_requests_to_reach_device_key_processor() {
async fn in_process_allows_device_key_requests_to_reach_device_key_api() {
let client = start_test_client(SessionSource::Cli).await;
const MALFORMED_KEY_ID_MESSAGE: &str = concat!(
"invalid device key payload: keyId must be dk_hse_, dk_tpm_, or dk_osn_ ",

View File

@@ -50,11 +50,11 @@ use codex_config::TextRange as CoreTextRange;
use codex_core::ExecPolicyError;
use codex_core::check_execpolicy_for_warnings;
use codex_core::config::find_codex_home;
use codex_core::init_state_db_from_config;
use codex_exec_server::EnvironmentManager;
use codex_exec_server::ExecServerRuntimePaths;
use codex_feedback::CodexFeedback;
use codex_protocol::protocol::SessionSource;
use codex_rollout::state_db as rollout_state_db;
use codex_state::log_db;
use tokio::sync::mpsc;
use tokio::sync::oneshot;
@@ -85,6 +85,7 @@ mod filters;
mod fs_watch;
mod fuzzy_file_search;
pub mod in_process;
mod mcp_refresh;
mod message_processor;
mod models;
mod outgoing_message;
@@ -487,9 +488,9 @@ pub async fn run_main_with_transport_options(
}
};
let state_db_result = rollout_state_db::try_init(&config).await;
let state_db_init_error = state_db_result.as_ref().err().map(ToString::to_string);
let state_db = state_db_result.ok();
let state_db = init_state_db_from_config(&config)
.await
.ok_or_else(|| std::io::Error::other("failed to initialize sqlite state db"))?;
if should_run_personality_migration {
let effective_toml = config.config_layer_stack.effective_config();
@@ -598,10 +599,12 @@ pub async fn run_main_with_transport_options(
let feedback_layer = feedback.logger_layer();
let feedback_metadata_layer = feedback.metadata_layer();
let log_db = state_db.clone().map(log_db::start);
let log_db_layer = log_db
.clone()
.map(|layer| layer.with_filter(Targets::new().with_default(Level::TRACE)));
let log_db = log_db::start(state_db.clone());
let log_db_layer = Some(
log_db
.clone()
.with_filter(Targets::new().with_default(Level::TRACE)),
);
let otel_logger_layer = otel.as_ref().and_then(|o| o.logger_layer());
let otel_tracing_layer = otel.as_ref().and_then(|o| o.tracing_layer());
let _ = tracing_subscriber::registry()
@@ -618,10 +621,6 @@ pub async fn run_main_with_transport_options(
None => error!("{}", warning.summary),
}
}
if let Some(err) = &state_db_init_error {
error!("failed to initialize sqlite state db: {err}");
}
let transport_shutdown_token = CancellationToken::new();
let mut transport_accept_handles = Vec::<JoinHandle<()>>::new();
@@ -666,25 +665,17 @@ pub async fn run_main_with_transport_options(
let auth_manager =
AuthManager::shared_from_config(&config, /*enable_codex_api_key_env*/ false).await;
let remote_control_config_enabled = config.features.enabled(Feature::RemoteControl);
let remote_control_enabled = remote_control_config_enabled && state_db.is_some();
if remote_control_config_enabled && state_db.is_none() {
error!("remote control disabled because sqlite state db is unavailable");
}
let remote_control_enabled = config.features.enabled(Feature::RemoteControl);
if transport_accept_handles.is_empty() && !remote_control_enabled {
return Err(std::io::Error::new(
ErrorKind::InvalidInput,
if remote_control_config_enabled && state_db.is_none() {
"no transport configured; remote control disabled because sqlite state db is unavailable"
} else {
"no transport configured; use --listen or enable remote control"
},
"no transport configured; use --listen or enable remote control",
));
}
let (remote_control_accept_handle, remote_control_handle) = start_remote_control(
config.chatgpt_base_url.clone(),
state_db.clone(),
Some(state_db.clone()),
auth_manager.clone(),
transport_event_tx.clone(),
transport_shutdown_token.clone(),
@@ -768,7 +759,7 @@ pub async fn run_main_with_transport_options(
config_manager,
environment_manager,
feedback: feedback.clone(),
log_db,
log_db: Some(log_db),
state_db: state_db.clone(),
config_warnings,
session_source,

View File

@@ -0,0 +1,241 @@
use crate::config_manager::ConfigManager;
use codex_core::CodexThread;
use codex_core::ThreadManager;
use codex_core::config::Config;
use codex_protocol::ThreadId;
use codex_protocol::protocol::McpServerRefreshConfig;
use codex_protocol::protocol::Op;
use std::io;
use std::sync::Arc;
use tracing::warn;
pub(crate) async fn queue_strict_refresh(
thread_manager: &Arc<ThreadManager>,
config_manager: &ConfigManager,
) -> io::Result<()> {
config_manager
.load_latest_config(/*fallback_cwd*/ None)
.await?;
let mut refreshes = Vec::new();
for thread_id in thread_manager.list_thread_ids().await {
let thread = thread_manager
.get_thread(thread_id)
.await
.map_err(|err| io::Error::other(format!("failed to load thread {thread_id}: {err}")))?;
let config =
build_refresh_config(thread_manager, config_manager, thread.config().await).await?;
refreshes.push((thread_id, thread, config));
}
for (thread_id, thread, config) in refreshes {
queue_refresh(thread_id, thread, config).await?;
}
Ok(())
}
pub(crate) async fn queue_best_effort_refresh(
thread_manager: &Arc<ThreadManager>,
config_manager: &ConfigManager,
) {
for thread_id in thread_manager.list_thread_ids().await {
let thread = match thread_manager.get_thread(thread_id).await {
Ok(thread) => thread,
Err(err) => {
warn!("failed to load thread {thread_id} for MCP refresh: {err}");
continue;
}
};
let config =
match build_refresh_config(thread_manager, config_manager, thread.config().await).await
{
Ok(config) => config,
Err(err) => {
warn!("failed to build MCP refresh config for thread {thread_id}: {err}");
continue;
}
};
if let Err(err) = queue_refresh(thread_id, thread, config).await {
warn!("{err}");
}
}
}
async fn build_refresh_config(
thread_manager: &ThreadManager,
config_manager: &ConfigManager,
thread_config: Arc<Config>,
) -> io::Result<McpServerRefreshConfig> {
let config = config_manager
.load_latest_config_for_thread(thread_config.as_ref())
.await?;
let mcp_servers = thread_manager
.mcp_manager()
.configured_servers(&config)
.await;
Ok(McpServerRefreshConfig {
mcp_servers: serde_json::to_value(mcp_servers).map_err(io::Error::other)?,
mcp_oauth_credentials_store_mode: serde_json::to_value(
config.mcp_oauth_credentials_store_mode,
)
.map_err(io::Error::other)?,
})
}
async fn queue_refresh(
thread_id: ThreadId,
thread: Arc<CodexThread>,
config: McpServerRefreshConfig,
) -> io::Result<()> {
thread
.submit(Op::RefreshMcpServers { config })
.await
.map(|_| ())
.map_err(|err| {
io::Error::other(format!(
"failed to queue MCP refresh for thread {thread_id}: {err}"
))
})
}
#[cfg(test)]
mod tests {
use super::*;
use async_trait::async_trait;
use codex_arg0::Arg0DispatchPaths;
use codex_config::CloudRequirementsLoader;
use codex_config::LoaderOverrides;
use codex_config::ThreadConfigContext;
use codex_config::ThreadConfigLoadError;
use codex_config::ThreadConfigLoadErrorCode;
use codex_config::ThreadConfigLoader;
use codex_config::ThreadConfigSource;
use codex_core::agent_graph_store_from_state_db;
use codex_core::config::ConfigOverrides;
use codex_core::init_state_db_from_config;
use codex_core::thread_store_from_config;
use codex_exec_server::EnvironmentManager;
use codex_login::AuthManager;
use codex_login::CodexAuth;
use codex_protocol::protocol::SessionSource;
use codex_utils_absolute_path::AbsolutePathBuf;
use pretty_assertions::assert_eq;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use tempfile::TempDir;
#[tokio::test]
async fn strict_refresh_reports_thread_planning_failures() -> anyhow::Result<()> {
let (_temp_dir, thread_manager, config_manager, _loader) = refresh_test_state().await?;
let err = queue_strict_refresh(&thread_manager, &config_manager)
.await
.expect_err("strict refresh should fail");
assert_eq!(err.to_string(), "failed to load refresh config");
Ok(())
}
#[tokio::test]
async fn best_effort_refresh_attempts_every_loaded_thread() -> anyhow::Result<()> {
let (_temp_dir, thread_manager, config_manager, loader) = refresh_test_state().await?;
queue_best_effort_refresh(&thread_manager, &config_manager).await;
assert_eq!(loader.good_loads.load(Ordering::Relaxed), 1);
assert_eq!(loader.bad_loads.load(Ordering::Relaxed), 1);
Ok(())
}
async fn refresh_test_state() -> anyhow::Result<(
TempDir,
Arc<ThreadManager>,
ConfigManager,
Arc<CountingThreadConfigLoader>,
)> {
let temp_dir = TempDir::new()?;
let good_cwd = temp_dir.path().join("good");
let bad_cwd = temp_dir.path().join("bad");
std::fs::create_dir_all(&good_cwd)?;
std::fs::create_dir_all(&bad_cwd)?;
let initial_config_manager =
ConfigManager::without_managed_config_for_tests(temp_dir.path().to_path_buf());
let good_config = initial_config_manager
.load_for_cwd(
/*request_overrides*/ None,
ConfigOverrides::default(),
Some(good_cwd.clone()),
)
.await?;
let bad_config = initial_config_manager
.load_for_cwd(
/*request_overrides*/ None,
ConfigOverrides::default(),
Some(bad_cwd.clone()),
)
.await?;
let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("dummy"));
let state_db = init_state_db_from_config(&good_config)
.await
.expect("refresh tests require state db");
let thread_store = thread_store_from_config(&good_config, state_db.clone());
let agent_graph_store = agent_graph_store_from_state_db(state_db.clone());
let thread_manager = Arc::new(ThreadManager::new(
&good_config,
auth_manager,
SessionSource::Exec,
Arc::new(EnvironmentManager::default_for_tests()),
/*analytics_events_client*/ None,
state_db,
thread_store,
agent_graph_store,
));
thread_manager.start_thread(good_config).await?;
thread_manager.start_thread(bad_config).await?;
let loader = Arc::new(CountingThreadConfigLoader {
good_cwd: AbsolutePathBuf::try_from(good_cwd)?,
bad_cwd: AbsolutePathBuf::try_from(bad_cwd)?,
good_loads: AtomicUsize::new(0),
bad_loads: AtomicUsize::new(0),
});
let config_manager = ConfigManager::new(
temp_dir.path().to_path_buf(),
Vec::new(),
LoaderOverrides::without_managed_config_for_tests(),
CloudRequirementsLoader::default(),
Arg0DispatchPaths::default(),
loader.clone(),
);
Ok((temp_dir, thread_manager, config_manager, loader))
}
struct CountingThreadConfigLoader {
good_cwd: AbsolutePathBuf,
bad_cwd: AbsolutePathBuf,
good_loads: AtomicUsize,
bad_loads: AtomicUsize,
}
#[async_trait]
impl ThreadConfigLoader for CountingThreadConfigLoader {
async fn load(
&self,
context: ThreadConfigContext,
) -> Result<Vec<ThreadConfigSource>, ThreadConfigLoadError> {
if context.cwd.as_ref() == Some(&self.good_cwd) {
self.good_loads.fetch_add(1, Ordering::Relaxed);
}
if context.cwd.as_ref() == Some(&self.bad_cwd) {
self.bad_loads.fetch_add(1, Ordering::Relaxed);
return Err(ThreadConfigLoadError::new(
ThreadConfigLoadErrorCode::Internal,
/*status_code*/ None,
"failed to load refresh config",
));
}
Ok(Vec::new())
}
}
}

View File

@@ -61,6 +61,7 @@ use codex_app_server_protocol::experimental_required_message;
use codex_arg0::Arg0DispatchPaths;
use codex_chatgpt::workspace_settings;
use codex_core::ThreadManager;
use codex_core::agent_graph_store_from_state_db;
use codex_core::config::Config;
use codex_core::thread_store_from_config;
use codex_exec_server::EnvironmentManager;
@@ -254,7 +255,7 @@ pub(crate) struct MessageProcessorArgs {
pub(crate) environment_manager: Arc<EnvironmentManager>,
pub(crate) feedback: CodexFeedback,
pub(crate) log_db: Option<LogDbLayer>,
pub(crate) state_db: Option<StateDbHandle>,
pub(crate) state_db: StateDbHandle,
pub(crate) config_warnings: Vec<ConfigWarningNotification>,
pub(crate) session_source: SessionSource,
pub(crate) auth_manager: Arc<AuthManager>,
@@ -291,14 +292,16 @@ impl MessageProcessor {
// affect per-thread behavior, but they must not move newly started,
// resumed, or forked threads to a different persistence backend/root.
let thread_store = thread_store_from_config(config.as_ref(), state_db.clone());
let agent_graph_store = agent_graph_store_from_state_db(state_db.clone());
let thread_manager = Arc::new(ThreadManager::new(
config.as_ref(),
auth_manager.clone(),
session_source,
environment_manager,
Some(analytics_events_client.clone()),
Arc::clone(&thread_store),
state_db.clone(),
Arc::clone(&thread_store),
agent_graph_store.clone(),
));
thread_manager
.plugins_manager()
@@ -344,7 +347,7 @@ impl MessageProcessor {
Arc::clone(&config),
feedback,
log_db,
state_db.clone(),
Some(state_db.clone()),
);
let git_processor = GitRequestProcessor::new();
let initialize_processor = InitializeRequestProcessor::new(
@@ -395,7 +398,7 @@ impl MessageProcessor {
thread_watch_manager.clone(),
Arc::clone(&thread_list_state_permit),
thread_goal_processor.clone(),
state_db.clone(),
Some(state_db.clone()),
);
let turn_processor = TurnRequestProcessor::new(
auth_manager.clone(),
@@ -413,7 +416,7 @@ impl MessageProcessor {
if matches!(plugin_startup_tasks, crate::PluginStartupTasks::Start) {
// Keep plugin startup warmups aligned at app-server startup.
let on_effective_plugins_changed =
plugin_processor.effective_plugins_changed_callback((*config).clone());
plugin_processor.effective_plugins_changed_callback();
thread_manager
.plugins_manager()
.maybe_start_plugin_startup_tasks_for_config(
@@ -795,9 +798,9 @@ impl MessageProcessor {
);
if let Some(scope) = serialization_scope {
let key = RequestSerializationQueueKey::from_scope(connection_id, scope);
let (key, access) = RequestSerializationQueueKey::from_scope(connection_id, scope);
self.request_serialization_queues
.enqueue(key, request)
.enqueue(key, access, request)
.await;
} else {
tokio::spawn(async move {
@@ -1084,6 +1087,11 @@ impl MessageProcessor {
ClientRequest::PluginShareSave { params, .. } => {
self.plugin_processor.plugin_share_save(params).await
}
ClientRequest::PluginShareUpdateTargets { params, .. } => {
self.plugin_processor
.plugin_share_update_targets(params)
.await
}
ClientRequest::PluginShareList { params, .. } => {
self.plugin_processor.plugin_share_list(params).await
}

View File

@@ -32,6 +32,7 @@ use codex_config::CloudRequirementsLoader;
use codex_config::LoaderOverrides;
use codex_core::config::Config;
use codex_core::config::ConfigBuilder;
use codex_core::init_state_db_from_config;
use codex_exec_server::EnvironmentManager;
use codex_feedback::CodexFeedback;
use codex_login::AuthManager;
@@ -281,6 +282,9 @@ async fn build_test_processor(
outgoing_tx,
analytics_events_client.clone(),
));
let state_db = init_state_db_from_config(config.as_ref())
.await
.expect("tracing test processor requires state db");
let processor = Arc::new(MessageProcessor::new(MessageProcessorArgs {
outgoing,
analytics_events_client,
@@ -290,7 +294,7 @@ async fn build_test_processor(
environment_manager: Arc::new(EnvironmentManager::default_for_tests()),
feedback: CodexFeedback::new(),
log_db: None,
state_db: None,
state_db,
config_warnings: Vec::new(),
session_source: SessionSource::VSCode,
auth_manager,

View File

@@ -115,11 +115,17 @@ use codex_app_server_protocol::PluginReadParams;
use codex_app_server_protocol::PluginReadResponse;
use codex_app_server_protocol::PluginShareDeleteParams;
use codex_app_server_protocol::PluginShareDeleteResponse;
use codex_app_server_protocol::PluginShareDiscoverability;
use codex_app_server_protocol::PluginShareListItem;
use codex_app_server_protocol::PluginShareListParams;
use codex_app_server_protocol::PluginShareListResponse;
use codex_app_server_protocol::PluginSharePrincipal;
use codex_app_server_protocol::PluginSharePrincipalType;
use codex_app_server_protocol::PluginShareSaveParams;
use codex_app_server_protocol::PluginShareSaveResponse;
use codex_app_server_protocol::PluginShareTarget;
use codex_app_server_protocol::PluginShareUpdateTargetsParams;
use codex_app_server_protocol::PluginShareUpdateTargetsResponse;
use codex_app_server_protocol::PluginSkillReadParams;
use codex_app_server_protocol::PluginSkillReadResponse;
use codex_app_server_protocol::PluginSource;
@@ -355,7 +361,6 @@ use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::GitInfo as CoreGitInfo;
use codex_protocol::protocol::InitialHistory;
use codex_protocol::protocol::McpAuthStatus as CoreMcpAuthStatus;
use codex_protocol::protocol::McpServerRefreshConfig;
use codex_protocol::protocol::Op;
use codex_protocol::protocol::RateLimitSnapshot as CoreRateLimitSnapshot;
use codex_protocol::protocol::RealtimeVoicesList;

View File

@@ -168,7 +168,7 @@ impl AccountRequestProcessor {
{
Ok(config) => {
let refresh_thread_manager = Arc::clone(thread_manager);
let refresh_config = config.clone();
let refresh_config_manager = config_manager.clone();
thread_manager
.plugins_manager()
.maybe_start_remote_installed_plugins_cache_refresh(
@@ -177,7 +177,7 @@ impl AccountRequestProcessor {
Some(Arc::new(move || {
Self::spawn_effective_plugins_changed_task(
Arc::clone(&refresh_thread_manager),
refresh_config.clone(),
refresh_config_manager.clone(),
);
})),
);
@@ -190,19 +190,17 @@ impl AccountRequestProcessor {
}
}
fn spawn_effective_plugins_changed_task(thread_manager: Arc<ThreadManager>, config: Config) {
fn spawn_effective_plugins_changed_task(
thread_manager: Arc<ThreadManager>,
config_manager: ConfigManager,
) {
tokio::spawn(async move {
thread_manager.plugins_manager().clear_cache();
thread_manager.skills_manager().clear_cache();
if thread_manager.list_thread_ids().await.is_empty() {
return;
}
if let Err(err) =
McpRequestProcessor::queue_mcp_server_refresh_for_config(&thread_manager, &config)
.await
{
warn!("failed to queue MCP refresh after effective plugins changed: {err:?}");
}
crate::mcp_refresh::queue_best_effort_refresh(&thread_manager, &config_manager).await;
});
}

View File

@@ -1,4 +1,5 @@
use super::*;
use futures::StreamExt;
#[derive(Clone)]
pub(crate) struct CatalogRequestProcessor {
@@ -9,6 +10,8 @@ pub(crate) struct CatalogRequestProcessor {
pub(super) workspace_settings_cache: Arc<workspace_settings::WorkspaceSettingsCache>,
}
const SKILLS_LIST_CWD_CONCURRENCY: usize = 8;
fn skills_to_info(
skills: &[codex_core::skills::SkillMetadata],
disabled_paths: &HashSet<AbsolutePathBuf>,
@@ -379,6 +382,7 @@ impl CatalogRequestProcessor {
&self,
params: SkillsListParams,
) -> Result<SkillsListResponse, JSONRPCErrorError> {
let total_started_at = Instant::now();
let SkillsListParams {
cwds,
force_reload,
@@ -390,7 +394,9 @@ impl CatalogRequestProcessor {
cwds
};
let cwd_set: HashSet<PathBuf> = cwds.iter().cloned().collect();
let cwd_count = cwds.len();
let extra_roots_started_at = Instant::now();
let mut extra_roots_by_cwd: HashMap<PathBuf, Vec<AbsolutePathBuf>> = HashMap::new();
for entry in per_cwd_extra_user_roots.unwrap_or_default() {
if !cwd_set.contains(&entry.cwd) {
@@ -417,12 +423,20 @@ impl CatalogRequestProcessor {
.or_default()
.extend(valid_extra_roots);
}
let extra_roots_ms = extra_roots_started_at.elapsed().as_millis();
let extra_root_count = extra_roots_by_cwd.values().map(Vec::len).sum::<usize>();
let load_config_started_at = Instant::now();
let config = self.load_latest_config(/*fallback_cwd*/ None).await?;
let load_config_ms = load_config_started_at.elapsed().as_millis();
let auth_started_at = Instant::now();
let auth = self.auth_manager.auth().await;
let auth_ms = auth_started_at.elapsed().as_millis();
let workspace_setting_started_at = Instant::now();
let workspace_codex_plugins_enabled = self
.workspace_codex_plugins_enabled(&config, auth.as_ref())
.await;
let workspace_setting_ms = workspace_setting_started_at.elapsed().as_millis();
let skills_manager = self.thread_manager.skills_manager();
let plugins_manager = self.thread_manager.plugins_manager();
let fs = self
@@ -430,56 +444,124 @@ impl CatalogRequestProcessor {
.environment_manager()
.default_environment()
.map(|environment| environment.get_filesystem());
let mut data = Vec::new();
for cwd in cwds {
let (cwd_abs, config_layer_stack) = match self.resolve_cwd_config(&cwd).await {
Ok(resolved) => resolved,
Err(message) => {
let error_path = cwd.clone();
data.push(codex_app_server_protocol::SkillsListEntry {
cwd,
skills: Vec::new(),
errors: vec![codex_app_server_protocol::SkillErrorInfo {
path: error_path,
message,
}],
});
continue;
let mut data = futures::stream::iter(cwds.into_iter().enumerate())
.map(|(index, cwd)| {
let config = &config;
let extra_roots_by_cwd = &extra_roots_by_cwd;
let fs = fs.clone();
let plugins_manager = &plugins_manager;
let skills_manager = &skills_manager;
async move {
let cwd_started_at = Instant::now();
let resolve_cwd_config_started_at = Instant::now();
let (cwd_abs, config_layer_stack) =
match self.resolve_cwd_config(&cwd).await {
Ok(resolved) => resolved,
Err(message) => {
warn!(
cwd = %cwd.display(),
total_ms = cwd_started_at.elapsed().as_millis(),
resolve_cwd_config_ms = resolve_cwd_config_started_at.elapsed().as_millis(),
"skills/list cwd timing failed to resolve cwd config"
);
let error_path = cwd.clone();
return (
index,
codex_app_server_protocol::SkillsListEntry {
cwd,
skills: Vec::new(),
errors: vec![
codex_app_server_protocol::SkillErrorInfo {
path: error_path,
message,
},
],
},
);
}
};
let resolve_cwd_config_ms =
resolve_cwd_config_started_at.elapsed().as_millis();
let extra_roots = extra_roots_by_cwd
.get(&cwd)
.map_or(&[][..], std::vec::Vec::as_slice);
let effective_skill_roots_started_at = Instant::now();
let effective_skill_roots = if workspace_codex_plugins_enabled {
let plugins_input = config.plugins_config_input();
plugins_manager
.effective_skill_roots_for_layer_stack(
&config_layer_stack,
&plugins_input,
)
.await
} else {
Vec::new()
};
let effective_skill_roots_ms =
effective_skill_roots_started_at.elapsed().as_millis();
let effective_skill_root_count = effective_skill_roots.len();
let skills_input = codex_core::skills::SkillsLoadInput::new(
cwd_abs.clone(),
effective_skill_roots,
config_layer_stack,
config.bundled_skills_enabled(),
);
let load_skills_started_at = Instant::now();
let outcome = skills_manager
.skills_for_cwd_with_extra_user_roots(
&skills_input,
force_reload,
extra_roots,
fs,
)
.await;
let load_skills_ms = load_skills_started_at.elapsed().as_millis();
let errors = errors_to_info(&outcome.errors);
let skills = skills_to_info(&outcome.skills, &outcome.disabled_paths);
warn!(
cwd = %cwd.display(),
total_ms = cwd_started_at.elapsed().as_millis(),
resolve_cwd_config_ms,
effective_skill_roots_ms,
load_skills_ms,
extra_root_count = extra_roots.len(),
effective_skill_root_count,
skill_count = skills.len(),
error_count = errors.len(),
"skills/list cwd timing"
);
(
index,
codex_app_server_protocol::SkillsListEntry {
cwd,
skills,
errors,
},
)
}
};
let extra_roots = extra_roots_by_cwd
.get(&cwd)
.map_or(&[][..], std::vec::Vec::as_slice);
let effective_skill_roots = if workspace_codex_plugins_enabled {
let plugins_input = config.plugins_config_input();
plugins_manager
.effective_skill_roots_for_layer_stack(&config_layer_stack, &plugins_input)
.await
} else {
Vec::new()
};
let skills_input = codex_core::skills::SkillsLoadInput::new(
cwd_abs.clone(),
effective_skill_roots,
config_layer_stack,
config.bundled_skills_enabled(),
);
let outcome = skills_manager
.skills_for_cwd_with_extra_user_roots(
&skills_input,
force_reload,
extra_roots,
fs.clone(),
)
.await;
let errors = errors_to_info(&outcome.errors);
let skills = skills_to_info(&outcome.skills, &outcome.disabled_paths);
data.push(codex_app_server_protocol::SkillsListEntry {
cwd,
skills,
errors,
});
}
})
.buffer_unordered(SKILLS_LIST_CWD_CONCURRENCY)
.collect::<Vec<_>>()
.await;
data.sort_unstable_by_key(|(index, _)| *index);
let data = data.into_iter().map(|(_, entry)| entry).collect::<Vec<_>>();
let skill_count = data.iter().map(|entry| entry.skills.len()).sum::<usize>();
let error_count = data.iter().map(|entry| entry.errors.len()).sum::<usize>();
warn!(
cwd_count,
total_ms = total_started_at.elapsed().as_millis(),
force_reload,
extra_roots_ms,
extra_root_count,
load_config_ms,
auth_ms,
workspace_setting_ms,
workspace_codex_plugins_enabled,
has_remote_fs = fs.is_some(),
skill_count,
error_count,
"skills/list timing"
);
Ok(SkillsListResponse { data })
}

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