feat(app-server-protocol): freeze the selected Codex v2 contract as a generated manifest (CW-7704 V2-01)

contract/codex-v2.json is now the one source for the Codex App Server v2
contract: every pinned rust-v0.166.0 stable method with its tier, status,
basis, upstream variant and params/result types, the dispatch rule per
tier, the error codes and the Codewhale extensions. src/codegen reads it
with the unmodified pinned schema member and generates src/methods.rs,
src/v2.rs (319 typed definitions: params, results, notification payloads,
server-request answers, the full ThreadItem/UserInput/Thread/Turn unions
and their item unions, plus typed ClientRequestParams, ClientResponse,
ServerRequestParams, ServerRequestResponse and ServerNotification
envelopes), METHODS.md, schema/json/codex-v2.schema.json and
schema/typescript/codex-v2.d.ts. This replaces the hand-kept methods.rs
macro and METHODS.md table, whose agreement tests/methods.rs only checked
after the fact.

Selection is unchanged: 44 A/B client requests, 1 client notification,
5 server requests, 33 server notifications. Tier C and undecided rows
(124) answer -32601 and are never emitted; the 29 undecided rows with an
A/B recommendation stay undecided pending the founder.

Shapes the earlier hand types dropped are now typed: ThreadItem's shared
startedAtMs/completedAtMs beside its tagged kind, and the url/fileId
alternatives inside image inputs and function-call output items.

Codewhale extensions: Thread.executor (ThreadExecutor) and
Thread.environment (ThreadPlacement, named apart from Codex
ThreadEnvironment) stay optional and credential-free; rpc re-exports them
under their old names. CAPABILITY_UNSUPPORTED (-32020) with
CapabilityRefusalData carries the executor/placement refusal codes.

Pinned: tests/pinned/codex/codex_app_server_protocol.schemas.json
(stable export member, sha256 9b6d5364...) and common.rs (git blob
2936c018, read only by the result-type check).

Checks (local, dev-cargo.sh, macOS): cargo test --locked -p
codewhale-app-server-protocol: lib 10, contract 7, wire 3 passed;
with --features schema also schema 2 passed. The contract test
round-trips 1064 schema-derived instances over 303 pinned definitions
and validates input and output with jsonschema; removing one Thread
field from src/v2.rs made it fail. clippy -D warnings (both feature
sets), cargo fmt --check, codewhale-codex-contract --check and
codewhale-app-server-schema --check clean; Cargo.lock unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
CodeWhale BotandClaude Opus 5.5 committed 2026-11-10 05:07:01 -08:00
1 parent dde4b6542d
commit acc0e30f65
17 files changed
+54134 -607

No files matched your search

+4
View File
@@ -22,6 +22,10 @@ schemars = { version = "1.2.1", optional = true, features = ["derive", "preserve
[dev-dependencies]
jsonschema.workspace = true
[[bin]]
name = "codewhale-codex-contract"
path = "src/bin/contract.rs"
[[bin]]
name = "codewhale-app-server-schema"
path = "src/bin/export.rs"
+257 -224
View File
@@ -1,252 +1,285 @@
# Codex App Server method inventory
Every method in the pinned Codex `rust-v0.166.0` stable wire enums (commit `6ff1e1ca3b30fc8b914720681fcf5fe62e95f856`), with the tier Codewhale gives it and what is on main. The method sets are the unmodified export members under `tests/pinned/codex/`; `tests/methods.rs` fails when this file, those members and the typed enums in `src/methods.rs` disagree.
Generated from `contract/codex-v2.json` (contract `codewhale-codex-app-server-v2` version 1) and the pinned Codex `rust-v0.166.0` export (commit `6ff1e1ca3b30fc8b914720681fcf5fe62e95f856`) by `cargo run -p codewhale-app-server-protocol --bin codewhale-codex-contract -- --write`. Edit the manifest, never this file; `tests/contract.rs` fails when they drift. The method sets are the unmodified export members under `tests/pinned/codex/`.
- **A**: required for 0.10.3 Codex parity.
- **B**: service methods, required for 0.10.3.
- **C**: out of scope, for the reason given. A Tier C client request answers `-32601` method not found; the server never sends a Tier C request or notification.
- **undecided**: the 0.10.3 parity bar does not settle it yet. The last column carries a recommended tier for the founder to confirm.
- **C**: out of scope, for the reason given.
- **undecided**: the 0.10.3 parity bar does not settle it yet. The last column carries a recommended tier for the founder to confirm; until then it is handled exactly like Tier C.
Status: `implemented` means a Codex-shaped handler is on main (response goldens are not yet checked); `partial` means the Codex method name dispatches to the legacy shape; `missing` means no handler; `method-not-found` means the dispatcher answers `-32601`; `never-sent` means the server never emits it. The stable export is the scope: methods that exist only in the experimental export are not listed.
Status: `implemented` means a Codex-shaped handler is on main (response goldens are not yet checked); `partial` means the Codex method name dispatches to the legacy shape; `missing` means no handler; `method-not-found` means the dispatcher answers `-32601`; `never-sent` means the server never emits it. The stable export is the scope: methods that exist only in the experimental export are not listed. A complete inventory or typed shape is not a handler: status, not tier, says what main dispatches.
## Dispatch rules
| Tier | Rule |
| --- | --- |
| A | selected for 0.10.3; the typed params/result in src/v2.rs are the wire contract |
| B | selected for 0.10.3 (service methods); the typed params/result in src/v2.rs are the wire contract |
| C | not selected; a client request answers methodNotFound (-32601); the server never sends a Tier C request or notification |
| undecided | not selected until the founder decides; until then it is handled exactly like Tier C (methodNotFound, never sent), and the row keeps its recommendation |
## Client requests
| Method | Tier | Status | Basis | Recommended |
| --- | --- | --- | --- | --- |
| `initialize` | A | implemented | connection lifecycle (CW-7704; on main) | |
| `thread/start` | A | implemented | thread lifecycle | |
| `thread/resume` | A | partial | thread lifecycle | |
| `thread/fork` | A | partial | thread lifecycle | |
| `thread/archive` | A | partial | thread lifecycle | |
| `thread/delete` | B | missing | S9 services: thread delete, revert, shellCommand, compact | |
| `thread/unsubscribe` | A | missing | S5 subscriptions and paging | |
| `thread/name/set` | A | implemented | thread lifecycle | |
| `thread/goal/set` | A | partial | S8 goals | |
| `thread/goal/get` | A | partial | S8 goals | |
| `thread/goal/clear` | A | partial | S8 goals | |
| `thread/metadata/update` | undecided | missing | thread metadata beyond name/goal | B |
| `thread/attachment/add` | undecided | missing | Codex desktop attachments | C |
| `thread/attachment/list` | undecided | missing | Codex desktop attachments | C |
| `thread/attachmentOwner/list` | undecided | missing | Codex desktop attachments | C |
| `thread/attachment/remove` | undecided | missing | Codex desktop attachments | C |
| `thread/section/move` | undecided | missing | Codex desktop sidebar sections; Codewhale projects are CW-7878 | C |
| `thread/unarchive` | A | partial | thread lifecycle | |
| `thread/compact/start` | B | missing | S9 services: thread delete, revert, shellCommand, compact | |
| `thread/shellCommand` | B | missing | S9 services: thread delete, revert, shellCommand, compact | |
| `thread/approveGuardianDeniedAction` | undecided | missing | Codex Guardian auto-review override; Codewhale has its own Auto-Review posture | C |
| `thread/revert` | B | missing | S9 services: thread delete, revert, shellCommand, compact | |
| `thread/list` | A | implemented | thread lifecycle | |
| `threadSection/list` | undecided | missing | Codex desktop sidebar sections; Codewhale projects are CW-7878 | C |
| `threadSection/create` | undecided | missing | Codex desktop sidebar sections; Codewhale projects are CW-7878 | C |
| `threadSection/update` | undecided | missing | Codex desktop sidebar sections; Codewhale projects are CW-7878 | C |
| `threadSection/delete` | undecided | missing | Codex desktop sidebar sections; Codewhale projects are CW-7878 | C |
| `thread/loaded/list` | A | implemented | thread lifecycle | |
| `thread/read` | A | implemented | thread lifecycle | |
| `thread/turns/list` | A | missing | S5 subscriptions and paging | |
| `thread/items/list` | A | missing | S5 subscriptions and paging | |
| `thread/inject_items` | undecided | missing | raw history injection | C |
| `skills/list` | B | missing | S9 services: skills | |
| `skills/extraRoots/set` | undecided | missing | skills configuration writes | B |
| `hooks/list` | B | missing | S9 services: hooks | |
| `marketplace/add` | undecided | missing | Codex plugin marketplace; Codewhale plugins are DSH (CW-7051) | C |
| `marketplace/remove` | undecided | missing | Codex plugin marketplace; Codewhale plugins are DSH (CW-7051) | C |
| `marketplace/upgrade` | undecided | missing | Codex plugin marketplace; Codewhale plugins are DSH (CW-7051) | C |
| `plugin/list` | undecided | missing | Codex plugin marketplace; Codewhale plugins are DSH (CW-7051) | C |
| `plugin/installed` | undecided | missing | Codex plugin marketplace; Codewhale plugins are DSH (CW-7051) | C |
| `plugin/reconcile` | undecided | missing | Codex plugin marketplace; Codewhale plugins are DSH (CW-7051) | C |
| `plugin/read` | undecided | missing | Codex plugin marketplace; Codewhale plugins are DSH (CW-7051) | C |
| `plugin/skill/read` | undecided | missing | Codex plugin marketplace; Codewhale plugins are DSH (CW-7051) | C |
| `plugin/share/save` | C | method-not-found | OpenAI-hosted plugin sharing; no Codewhale service | |
| `plugin/share/updateTargets` | C | method-not-found | OpenAI-hosted plugin sharing; no Codewhale service | |
| `plugin/share/list` | C | method-not-found | OpenAI-hosted plugin sharing; no Codewhale service | |
| `plugin/share/checkout` | C | method-not-found | OpenAI-hosted plugin sharing; no Codewhale service | |
| `plugin/share/delete` | C | method-not-found | OpenAI-hosted plugin sharing; no Codewhale service | |
| `app/read` | C | method-not-found | ChatGPT Apps connectors; no Codewhale service | |
| `app/list` | C | method-not-found | ChatGPT Apps connectors; no Codewhale service | |
| `app/installed` | C | method-not-found | ChatGPT Apps connectors; no Codewhale service | |
| `fs/readFile` | B | missing | S9 services: fs | |
| `fs/writeFile` | B | missing | S9 services: fs | |
| `fs/createDirectory` | B | missing | S9 services: fs | |
| `fs/getMetadata` | B | missing | S9 services: fs | |
| `fs/readDirectory` | B | missing | S9 services: fs | |
| `fs/remove` | B | missing | S9 services: fs | |
| `fs/copy` | B | missing | S9 services: fs | |
| `fs/watch` | B | missing | S9 services: fs | |
| `fs/unwatch` | B | missing | S9 services: fs | |
| `skills/config/write` | undecided | missing | skills configuration writes | B |
| `plugin/install` | undecided | missing | Codex plugin marketplace; Codewhale plugins are DSH (CW-7051) | C |
| `plugin/uninstall` | undecided | missing | Codex plugin marketplace; Codewhale plugins are DSH (CW-7051) | C |
| `turn/start` | A | missing | S4a full-tool turns | |
| `turn/steer` | A | missing | S4a full-tool turns | |
| `turn/interrupt` | A | missing | S4a full-tool turns | |
| `review/start` | B | missing | S9 services: review | |
| `model/list` | B | missing | S9 services: models | |
| `account/gatewayOAuth/read` | C | method-not-found | OpenAI gateway OAuth; no Codewhale service | |
| `account/gatewayOAuth/login` | C | method-not-found | OpenAI gateway OAuth; no Codewhale service | |
| `account/gatewayOAuth/cancel` | C | method-not-found | OpenAI gateway OAuth; no Codewhale service | |
| `modelProvider/capabilities/read` | undecided | missing | provider, feature and permission catalogs | B |
| `experimentalFeature/list` | undecided | missing | Codex experimental feature flags | C |
| `permissionProfile/list` | undecided | missing | provider, feature and permission catalogs | B |
| `experimentalFeature/enablement/set` | undecided | missing | Codex experimental feature flags | C |
| `mcpServer/oauth/login` | undecided | missing | MCP actions beyond status | B |
| `config/mcpServer/reload` | undecided | missing | MCP actions beyond status | B |
| `mcpServerStatus/list` | B | missing | S9 services: MCP status | |
| `mcpServer/resource/read` | undecided | missing | MCP actions beyond status | B |
| `mcpServer/tool/call` | undecided | missing | MCP actions beyond status | B |
| `windowsSandbox/setupStart` | undecided | missing | Codex Windows sandbox setup; Codewhale has its own AppContainer sandbox | C |
| `windowsSandbox/readiness` | undecided | missing | Codex Windows sandbox setup; Codewhale has its own AppContainer sandbox | C |
| `account/login/start` | undecided | missing | OpenAI account sign-in and usage; Codewhale signs in through /auth | C |
| `account/login/cancel` | undecided | missing | OpenAI account sign-in and usage; Codewhale signs in through /auth | C |
| `account/logout` | undecided | missing | OpenAI account sign-in and usage; Codewhale signs in through /auth | C |
| `account/rateLimits/read` | undecided | missing | OpenAI account sign-in and usage; Codewhale signs in through /auth | C |
| `account/rateLimitResetCredit/consume` | C | method-not-found | OpenAI billing and workspace messaging; no Codewhale service | |
| `account/usage/read` | undecided | missing | OpenAI account sign-in and usage; Codewhale signs in through /auth | C |
| `account/workspaceMessages/read` | C | method-not-found | OpenAI billing and workspace messaging; no Codewhale service | |
| `account/sendAddCreditsNudgeEmail` | C | method-not-found | OpenAI billing and workspace messaging; no Codewhale service | |
| `feedback/upload` | C | method-not-found | uploads feedback to OpenAI; no Codewhale service | |
| `feedback/save` | C | method-not-found | uploads feedback to OpenAI; no Codewhale service | |
| `command/exec` | B | missing | S9 services: command/exec | |
| `command/exec/write` | B | missing | S9 services: command/exec | |
| `command/exec/terminate` | B | missing | S9 services: command/exec | |
| `command/exec/resize` | B | missing | S9 services: command/exec | |
| `config/read` | B | missing | S9 services: config | |
| `externalAgentConfig/detect` | undecided | missing | import config from other agents; Codewhale external agents are CW-7879 | C |
| `externalAgentConfig/import` | undecided | missing | import config from other agents; Codewhale external agents are CW-7879 | C |
| `externalAgentConfig/import/recordHistory` | undecided | missing | import config from other agents; Codewhale external agents are CW-7879 | C |
| `externalAgentConfig/import/readHistories` | undecided | missing | import config from other agents; Codewhale external agents are CW-7879 | C |
| `config/value/write` | B | missing | S9 services: config | |
| `config/batchWrite` | B | missing | S9 services: config | |
| `configRequirements/read` | undecided | missing | managed config requirements | B |
| `account/read` | undecided | missing | OpenAI account sign-in and usage; Codewhale signs in through /auth | C |
| `getConversationSummary` | C | method-not-found | deprecated v1 API (upstream marks it DEPRECATED; TypeScript-only in the export) | |
| `gitDiffToRemote` | C | method-not-found | deprecated v1 API (upstream marks it DEPRECATED; TypeScript-only in the export) | |
| `getAuthStatus` | C | method-not-found | deprecated v1 API (upstream marks it DEPRECATED; TypeScript-only in the export) | |
| `fuzzyFileSearch` | undecided | missing | file search | B |
| Method | Tier | Status | Basis | Recommended | Params | Result |
| --- | --- | --- | --- | --- | --- | --- |
| `initialize` | A | implemented | connection lifecycle (CW-7704; on main) | | `InitializeParams` | `InitializeResponse` |
| `thread/start` | A | implemented | thread lifecycle | | `ThreadStartParams` | `ThreadStartResponse` |
| `thread/resume` | A | partial | thread lifecycle | | `ThreadResumeParams` | `ThreadResumeResponse` |
| `thread/fork` | A | partial | thread lifecycle | | `ThreadForkParams` | `ThreadForkResponse` |
| `thread/archive` | A | partial | thread lifecycle | | `ThreadArchiveParams` | `ThreadArchiveResponse` |
| `thread/delete` | B | missing | S9 services: thread delete, revert, shellCommand, compact | | `ThreadDeleteParams` | `ThreadDeleteResponse` |
| `thread/unsubscribe` | A | missing | S5 subscriptions and paging | | `ThreadUnsubscribeParams` | `ThreadUnsubscribeResponse` |
| `thread/name/set` | A | implemented | thread lifecycle | | `ThreadSetNameParams` | `ThreadSetNameResponse` |
| `thread/goal/set` | A | partial | S8 goals | | `ThreadGoalSetParams` | `ThreadGoalSetResponse` |
| `thread/goal/get` | A | partial | S8 goals | | `ThreadGoalGetParams` | `ThreadGoalGetResponse` |
| `thread/goal/clear` | A | partial | S8 goals | | `ThreadGoalClearParams` | `ThreadGoalClearResponse` |
| `thread/metadata/update` | undecided | missing | thread metadata beyond name/goal | B | ThreadMetadataUpdateParams (untyped) | ThreadMetadataUpdateResponse (untyped) |
| `thread/attachment/add` | undecided | missing | Codex desktop attachments | C | ThreadAttachmentAddParams (untyped) | ThreadAttachmentAddResponse (untyped) |
| `thread/attachment/list` | undecided | missing | Codex desktop attachments | C | ThreadAttachmentListParams (untyped) | ThreadAttachmentListResponse (untyped) |
| `thread/attachmentOwner/list` | undecided | missing | Codex desktop attachments | C | ThreadAttachmentOwnerListParams (untyped) | ThreadAttachmentOwnerListResponse (untyped) |
| `thread/attachment/remove` | undecided | missing | Codex desktop attachments | C | ThreadAttachmentRemoveParams (untyped) | ThreadAttachmentRemoveResponse (untyped) |
| `thread/section/move` | undecided | missing | Codex desktop sidebar sections; Codewhale projects are CW-7878 | C | ThreadSectionMoveParams (untyped) | ThreadSectionMoveResponse (untyped) |
| `thread/unarchive` | A | partial | thread lifecycle | | `ThreadUnarchiveParams` | `ThreadUnarchiveResponse` |
| `thread/compact/start` | B | missing | S9 services: thread delete, revert, shellCommand, compact | | `ThreadCompactStartParams` | `ThreadCompactStartResponse` |
| `thread/shellCommand` | B | missing | S9 services: thread delete, revert, shellCommand, compact | | `ThreadShellCommandParams` | `ThreadShellCommandResponse` |
| `thread/approveGuardianDeniedAction` | undecided | missing | Codex Guardian auto-review override; Codewhale has its own Auto-Review posture | C | ThreadApproveGuardianDeniedActionParams (untyped) | ThreadApproveGuardianDeniedActionResponse (untyped) |
| `thread/revert` | B | missing | S9 services: thread delete, revert, shellCommand, compact | | `ThreadRevertParams` | `ThreadRevertResponse` |
| `thread/list` | A | implemented | thread lifecycle | | `ThreadListParams` | `ThreadListResponse` |
| `threadSection/list` | undecided | missing | Codex desktop sidebar sections; Codewhale projects are CW-7878 | C | ThreadSectionListParams (untyped) | ThreadSectionListResponse (untyped) |
| `threadSection/create` | undecided | missing | Codex desktop sidebar sections; Codewhale projects are CW-7878 | C | ThreadSectionCreateParams (untyped) | ThreadSectionCreateResponse (untyped) |
| `threadSection/update` | undecided | missing | Codex desktop sidebar sections; Codewhale projects are CW-7878 | C | ThreadSectionUpdateParams (untyped) | ThreadSectionUpdateResponse (untyped) |
| `threadSection/delete` | undecided | missing | Codex desktop sidebar sections; Codewhale projects are CW-7878 | C | ThreadSectionDeleteParams (untyped) | ThreadSectionDeleteResponse (untyped) |
| `thread/loaded/list` | A | implemented | thread lifecycle | | `ThreadLoadedListParams` | `ThreadLoadedListResponse` |
| `thread/read` | A | implemented | thread lifecycle | | `ThreadReadParams` | `ThreadReadResponse` |
| `thread/turns/list` | A | missing | S5 subscriptions and paging | | `ThreadTurnsListParams` | `ThreadTurnsListResponse` |
| `thread/items/list` | A | missing | S5 subscriptions and paging | | `ThreadItemsListParams` | `ThreadItemsListResponse` |
| `thread/inject_items` | undecided | missing | raw history injection | C | ThreadInjectItemsParams (untyped) | ThreadInjectItemsResponse (untyped) |
| `skills/list` | B | missing | S9 services: skills | | `SkillsListParams` | `SkillsListResponse` |
| `skills/extraRoots/set` | undecided | missing | skills configuration writes | B | SkillsExtraRootsSetParams (untyped) | SkillsExtraRootsSetResponse (untyped) |
| `hooks/list` | B | missing | S9 services: hooks | | `HooksListParams` | `HooksListResponse` |
| `marketplace/add` | undecided | missing | Codex plugin marketplace; Codewhale plugins are DSH (CW-7051) | C | MarketplaceAddParams (untyped) | MarketplaceAddResponse (untyped) |
| `marketplace/remove` | undecided | missing | Codex plugin marketplace; Codewhale plugins are DSH (CW-7051) | C | MarketplaceRemoveParams (untyped) | MarketplaceRemoveResponse (untyped) |
| `marketplace/upgrade` | undecided | missing | Codex plugin marketplace; Codewhale plugins are DSH (CW-7051) | C | MarketplaceUpgradeParams (untyped) | MarketplaceUpgradeResponse (untyped) |
| `plugin/list` | undecided | missing | Codex plugin marketplace; Codewhale plugins are DSH (CW-7051) | C | PluginListParams (untyped) | PluginListResponse (untyped) |
| `plugin/installed` | undecided | missing | Codex plugin marketplace; Codewhale plugins are DSH (CW-7051) | C | PluginInstalledParams (untyped) | PluginInstalledResponse (untyped) |
| `plugin/reconcile` | undecided | missing | Codex plugin marketplace; Codewhale plugins are DSH (CW-7051) | C | PluginReconcileParams (untyped) | PluginReconcileResponse (untyped) |
| `plugin/read` | undecided | missing | Codex plugin marketplace; Codewhale plugins are DSH (CW-7051) | C | PluginReadParams (untyped) | PluginReadResponse (untyped) |
| `plugin/skill/read` | undecided | missing | Codex plugin marketplace; Codewhale plugins are DSH (CW-7051) | C | PluginSkillReadParams (untyped) | PluginSkillReadResponse (untyped) |
| `plugin/share/save` | C | method-not-found | OpenAI-hosted plugin sharing; no Codewhale service | | PluginShareSaveParams (untyped) | PluginShareSaveResponse (untyped) |
| `plugin/share/updateTargets` | C | method-not-found | OpenAI-hosted plugin sharing; no Codewhale service | | PluginShareUpdateTargetsParams (untyped) | PluginShareUpdateTargetsResponse (untyped) |
| `plugin/share/list` | C | method-not-found | OpenAI-hosted plugin sharing; no Codewhale service | | PluginShareListParams (untyped) | PluginShareListResponse (untyped) |
| `plugin/share/checkout` | C | method-not-found | OpenAI-hosted plugin sharing; no Codewhale service | | PluginShareCheckoutParams (untyped) | PluginShareCheckoutResponse (untyped) |
| `plugin/share/delete` | C | method-not-found | OpenAI-hosted plugin sharing; no Codewhale service | | PluginShareDeleteParams (untyped) | PluginShareDeleteResponse (untyped) |
| `app/read` | C | method-not-found | ChatGPT Apps connectors; no Codewhale service | | AppsReadParams (untyped) | AppsReadResponse (untyped) |
| `app/list` | C | method-not-found | ChatGPT Apps connectors; no Codewhale service | | AppsListParams (untyped) | AppsListResponse (untyped) |
| `app/installed` | C | method-not-found | ChatGPT Apps connectors; no Codewhale service | | AppsInstalledParams (untyped) | AppsInstalledResponse (untyped) |
| `fs/readFile` | B | missing | S9 services: fs | | `FsReadFileParams` | `FsReadFileResponse` |
| `fs/writeFile` | B | missing | S9 services: fs | | `FsWriteFileParams` | `FsWriteFileResponse` |
| `fs/createDirectory` | B | missing | S9 services: fs | | `FsCreateDirectoryParams` | `FsCreateDirectoryResponse` |
| `fs/getMetadata` | B | missing | S9 services: fs | | `FsGetMetadataParams` | `FsGetMetadataResponse` |
| `fs/readDirectory` | B | missing | S9 services: fs | | `FsReadDirectoryParams` | `FsReadDirectoryResponse` |
| `fs/remove` | B | missing | S9 services: fs | | `FsRemoveParams` | `FsRemoveResponse` |
| `fs/copy` | B | missing | S9 services: fs | | `FsCopyParams` | `FsCopyResponse` |
| `fs/watch` | B | missing | S9 services: fs | | `FsWatchParams` | `FsWatchResponse` |
| `fs/unwatch` | B | missing | S9 services: fs | | `FsUnwatchParams` | `FsUnwatchResponse` |
| `skills/config/write` | undecided | missing | skills configuration writes | B | SkillsConfigWriteParams (untyped) | SkillsConfigWriteResponse (untyped) |
| `plugin/install` | undecided | missing | Codex plugin marketplace; Codewhale plugins are DSH (CW-7051) | C | PluginInstallParams (untyped) | PluginInstallResponse (untyped) |
| `plugin/uninstall` | undecided | missing | Codex plugin marketplace; Codewhale plugins are DSH (CW-7051) | C | PluginUninstallParams (untyped) | PluginUninstallResponse (untyped) |
| `turn/start` | A | missing | S4a full-tool turns | | `TurnStartParams` | `TurnStartResponse` |
| `turn/steer` | A | missing | S4a full-tool turns | | `TurnSteerParams` | `TurnSteerResponse` |
| `turn/interrupt` | A | missing | S4a full-tool turns | | `TurnInterruptParams` | `TurnInterruptResponse` |
| `review/start` | B | missing | S9 services: review | | `ReviewStartParams` | `ReviewStartResponse` |
| `model/list` | B | missing | S9 services: models | | `ModelListParams` | `ModelListResponse` |
| `account/gatewayOAuth/read` | C | method-not-found | OpenAI gateway OAuth; no Codewhale service | | | |
| `account/gatewayOAuth/login` | C | method-not-found | OpenAI gateway OAuth; no Codewhale service | | | |
| `account/gatewayOAuth/cancel` | C | method-not-found | OpenAI gateway OAuth; no Codewhale service | | | |
| `modelProvider/capabilities/read` | undecided | missing | provider, feature and permission catalogs | B | ModelProviderCapabilitiesReadParams (untyped) | ModelProviderCapabilitiesReadResponse (untyped) |
| `experimentalFeature/list` | undecided | missing | Codex experimental feature flags | C | ExperimentalFeatureListParams (untyped) | ExperimentalFeatureListResponse (untyped) |
| `permissionProfile/list` | undecided | missing | provider, feature and permission catalogs | B | PermissionProfileListParams (untyped) | PermissionProfileListResponse (untyped) |
| `experimentalFeature/enablement/set` | undecided | missing | Codex experimental feature flags | C | ExperimentalFeatureEnablementSetParams (untyped) | ExperimentalFeatureEnablementSetResponse (untyped) |
| `mcpServer/oauth/login` | undecided | missing | MCP actions beyond status | B | McpServerOauthLoginParams (untyped) | McpServerOauthLoginResponse (untyped) |
| `config/mcpServer/reload` | undecided | missing | MCP actions beyond status | B | | |
| `mcpServerStatus/list` | B | missing | S9 services: MCP status | | `ListMcpServerStatusParams` | `ListMcpServerStatusResponse` |
| `mcpServer/resource/read` | undecided | missing | MCP actions beyond status | B | McpResourceReadParams (untyped) | McpResourceReadResponse (untyped) |
| `mcpServer/tool/call` | undecided | missing | MCP actions beyond status | B | McpServerToolCallParams (untyped) | McpServerToolCallResponse (untyped) |
| `windowsSandbox/setupStart` | undecided | missing | Codex Windows sandbox setup; Codewhale has its own AppContainer sandbox | C | WindowsSandboxSetupStartParams (untyped) | WindowsSandboxSetupStartResponse (untyped) |
| `windowsSandbox/readiness` | undecided | missing | Codex Windows sandbox setup; Codewhale has its own AppContainer sandbox | C | | |
| `account/login/start` | undecided | missing | OpenAI account sign-in and usage; Codewhale signs in through /auth | C | LoginAccountParams (untyped) | LoginAccountResponse (untyped) |
| `account/login/cancel` | undecided | missing | OpenAI account sign-in and usage; Codewhale signs in through /auth | C | CancelLoginAccountParams (untyped) | CancelLoginAccountResponse (untyped) |
| `account/logout` | undecided | missing | OpenAI account sign-in and usage; Codewhale signs in through /auth | C | | |
| `account/rateLimits/read` | undecided | missing | OpenAI account sign-in and usage; Codewhale signs in through /auth | C | | |
| `account/rateLimitResetCredit/consume` | C | method-not-found | OpenAI billing and workspace messaging; no Codewhale service | | ConsumeAccountRateLimitResetCreditParams (untyped) | ConsumeAccountRateLimitResetCreditResponse (untyped) |
| `account/usage/read` | undecided | missing | OpenAI account sign-in and usage; Codewhale signs in through /auth | C | | |
| `account/workspaceMessages/read` | C | method-not-found | OpenAI billing and workspace messaging; no Codewhale service | | | |
| `account/sendAddCreditsNudgeEmail` | C | method-not-found | OpenAI billing and workspace messaging; no Codewhale service | | SendAddCreditsNudgeEmailParams (untyped) | SendAddCreditsNudgeEmailResponse (untyped) |
| `feedback/upload` | C | method-not-found | uploads feedback to OpenAI; no Codewhale service | | FeedbackUploadParams (untyped) | FeedbackUploadResponse (untyped) |
| `feedback/save` | C | method-not-found | uploads feedback to OpenAI; no Codewhale service | | FeedbackSaveParams (untyped) | FeedbackSaveResponse (untyped) |
| `command/exec` | B | missing | S9 services: command/exec | | `CommandExecParams` | `CommandExecResponse` |
| `command/exec/write` | B | missing | S9 services: command/exec | | `CommandExecWriteParams` | `CommandExecWriteResponse` |
| `command/exec/terminate` | B | missing | S9 services: command/exec | | `CommandExecTerminateParams` | `CommandExecTerminateResponse` |
| `command/exec/resize` | B | missing | S9 services: command/exec | | `CommandExecResizeParams` | `CommandExecResizeResponse` |
| `config/read` | B | missing | S9 services: config | | `ConfigReadParams` | `ConfigReadResponse` |
| `externalAgentConfig/detect` | undecided | missing | import config from other agents; Codewhale external agents are CW-7879 | C | ExternalAgentConfigDetectParams (untyped) | ExternalAgentConfigDetectResponse (untyped) |
| `externalAgentConfig/import` | undecided | missing | import config from other agents; Codewhale external agents are CW-7879 | C | ExternalAgentConfigImportParams (untyped) | ExternalAgentConfigImportResponse (untyped) |
| `externalAgentConfig/import/recordHistory` | undecided | missing | import config from other agents; Codewhale external agents are CW-7879 | C | ExternalAgentConfigImportHistoryRecordParams (untyped) | ExternalAgentConfigImportHistoryRecordResponse (untyped) |
| `externalAgentConfig/import/readHistories` | undecided | missing | import config from other agents; Codewhale external agents are CW-7879 | C | | |
| `config/value/write` | B | missing | S9 services: config | | `ConfigValueWriteParams` | `ConfigWriteResponse` |
| `config/batchWrite` | B | missing | S9 services: config | | `ConfigBatchWriteParams` | `ConfigWriteResponse` |
| `configRequirements/read` | undecided | missing | managed config requirements | B | | |
| `account/read` | undecided | missing | OpenAI account sign-in and usage; Codewhale signs in through /auth | C | GetAccountParams (untyped) | GetAccountResponse (untyped) |
| `getConversationSummary` | C | method-not-found | deprecated v1 API (upstream marks it DEPRECATED; TypeScript-only in the export) | | GetConversationSummaryParams (untyped) | GetConversationSummaryResponse (untyped) |
| `gitDiffToRemote` | C | method-not-found | deprecated v1 API (upstream marks it DEPRECATED; TypeScript-only in the export) | | GitDiffToRemoteParams (untyped) | GitDiffToRemoteResponse (untyped) |
| `getAuthStatus` | C | method-not-found | deprecated v1 API (upstream marks it DEPRECATED; TypeScript-only in the export) | | GetAuthStatusParams (untyped) | GetAuthStatusResponse (untyped) |
| `fuzzyFileSearch` | undecided | missing | file search | B | FuzzyFileSearchParams (untyped) | FuzzyFileSearchResponse (untyped) |
## Client notifications
| Method | Tier | Status | Basis | Recommended |
| --- | --- | --- | --- | --- |
| `initialized` | A | implemented | connection lifecycle (CW-7704; on main) | |
| Method | Tier | Status | Basis | Recommended | Params | Result |
| --- | --- | --- | --- | --- | --- | --- |
| `initialized` | A | implemented | connection lifecycle (CW-7704; on main) | | | |
## Server requests
| Method | Tier | Status | Basis | Recommended |
| --- | --- | --- | --- | --- |
| `item/commandExecution/requestApproval` | A | missing | S6 interactions (CW-7704: bidirectional approvals, input, client tools) | |
| `item/fileChange/requestApproval` | A | missing | S6 interactions (CW-7704: bidirectional approvals, input, client tools) | |
| `item/tool/requestUserInput` | A | missing | S6 interactions (CW-7704: bidirectional approvals, input, client tools) | |
| `mcpServer/elicitation/request` | undecided | missing | MCP elicitation | A |
| `item/permissions/requestApproval` | A | missing | S6 interactions (CW-7704: bidirectional approvals, input, client tools) | |
| `item/tool/call` | A | missing | S6 interactions (CW-7704: bidirectional approvals, input, client tools) | |
| `account/chatgptAuthTokens/refresh` | C | never-sent | ChatGPT externally managed auth tokens; no Codewhale service | |
| `attestation/generate` | C | never-sent | OpenAI request attestation; no Codewhale service | |
| `applyPatchApproval` | C | never-sent | deprecated v1 approvals for legacy turns; superseded by item/*/requestApproval | |
| `execCommandApproval` | C | never-sent | deprecated v1 approvals for legacy turns; superseded by item/*/requestApproval | |
| Method | Tier | Status | Basis | Recommended | Params | Result |
| --- | --- | --- | --- | --- | --- | --- |
| `item/commandExecution/requestApproval` | A | missing | S6 interactions (CW-7704: bidirectional approvals, input, client tools) | | `CommandExecutionRequestApprovalParams` | `CommandExecutionRequestApprovalResponse` |
| `item/fileChange/requestApproval` | A | missing | S6 interactions (CW-7704: bidirectional approvals, input, client tools) | | `FileChangeRequestApprovalParams` | `FileChangeRequestApprovalResponse` |
| `item/tool/requestUserInput` | A | missing | S6 interactions (CW-7704: bidirectional approvals, input, client tools) | | `ToolRequestUserInputParams` | `ToolRequestUserInputResponse` |
| `mcpServer/elicitation/request` | undecided | missing | MCP elicitation | A | McpServerElicitationRequestParams (untyped) | McpServerElicitationRequestResponse (untyped) |
| `item/permissions/requestApproval` | A | missing | S6 interactions (CW-7704: bidirectional approvals, input, client tools) | | `PermissionsRequestApprovalParams` | `PermissionsRequestApprovalResponse` |
| `item/tool/call` | A | missing | S6 interactions (CW-7704: bidirectional approvals, input, client tools) | | `DynamicToolCallParams` | `DynamicToolCallResponse` |
| `account/chatgptAuthTokens/refresh` | C | never-sent | ChatGPT externally managed auth tokens; no Codewhale service | | ChatgptAuthTokensRefreshParams (untyped) | ChatgptAuthTokensRefreshResponse (untyped) |
| `attestation/generate` | C | never-sent | OpenAI request attestation; no Codewhale service | | AttestationGenerateParams (untyped) | AttestationGenerateResponse (untyped) |
| `applyPatchApproval` | C | never-sent | deprecated v1 approvals for legacy turns; superseded by item/*/requestApproval | | ApplyPatchApprovalParams (untyped) | ApplyPatchApprovalResponse (untyped) |
| `execCommandApproval` | C | never-sent | deprecated v1 approvals for legacy turns; superseded by item/*/requestApproval | | ExecCommandApprovalParams (untyped) | ExecCommandApprovalResponse (untyped) |
## Server notifications
| Method | Tier | Status | Basis | Recommended |
| Method | Tier | Status | Basis | Recommended | Params | Result |
| --- | --- | --- | --- | --- | --- | --- |
| `error` | A | missing | S3 projection: errors | | `ErrorNotification` | |
| `thread/started` | A | missing | S3 projection: thread lifecycle | | `ThreadStartedNotification` | |
| `thread/status/changed` | A | missing | S3 projection: thread lifecycle | | `ThreadStatusChangedNotification` | |
| `thread/readState/changed` | undecided | missing | thread state beyond the Tier A set | A | ThreadReadStateChangedNotification (untyped) | |
| `thread/archived` | A | missing | S3 projection: thread lifecycle | | `ThreadArchivedNotification` | |
| `thread/deleted` | B | missing | pairs with a Tier B service | | `ThreadDeletedNotification` | |
| `thread/unarchived` | A | missing | S3 projection: thread lifecycle | | `ThreadUnarchivedNotification` | |
| `thread/closed` | A | missing | S3 projection: thread lifecycle | | `ThreadClosedNotification` | |
| `thread/reverted` | B | missing | pairs with a Tier B service | | `ThreadRevertedNotification` | |
| `skills/changed` | B | missing | pairs with a Tier B service | | `SkillsChangedNotification` | |
| `thread/name/updated` | A | missing | S3 projection: thread lifecycle | | `ThreadNameUpdatedNotification` | |
| `thread/attachment/updated` | undecided | missing | Codex desktop attachments, predictions, projects and environments | C | ThreadAttachmentUpdatedNotification (untyped) | |
| `thread/goal/updated` | A | missing | S3 projection with S8 goals | | `ThreadGoalUpdatedNotification` | |
| `thread/prediction/updated` | undecided | missing | Codex desktop attachments, predictions, projects and environments | C | ThreadPredictionUpdatedNotification (untyped) | |
| `thread/goal/cleared` | A | missing | S3 projection with S8 goals | | `ThreadGoalClearedNotification` | |
| `thread/queue/changed` | undecided | missing | thread state beyond the Tier A set | A | ThreadQueueChangedNotification (untyped) | |
| `project/changed` | undecided | missing | Codex desktop attachments, predictions, projects and environments | C | ProjectChangedNotification (untyped) | |
| `thread/project/updated` | undecided | missing | Codex desktop attachments, predictions, projects and environments | C | ThreadProjectUpdatedNotification (untyped) | |
| `thread/environment/connected` | undecided | missing | Codex desktop attachments, predictions, projects and environments | C | EnvironmentConnectionNotification (untyped) | |
| `thread/environment/disconnected` | undecided | missing | Codex desktop attachments, predictions, projects and environments | C | EnvironmentConnectionNotification (untyped) | |
| `thread/settings/updated` | undecided | missing | thread state beyond the Tier A set | A | ThreadSettingsUpdatedNotification (untyped) | |
| `thread/tokenUsage/updated` | A | missing | S3 projection: thread lifecycle | | `ThreadTokenUsageUpdatedNotification` | |
| `turn/started` | A | missing | S3 projection: turn/* | | `TurnStartedNotification` | |
| `hook/started` | undecided | missing | hook runs; hooks/list is Tier B | B | HookStartedNotification (untyped) | |
| `turn/completed` | A | missing | S3 projection: turn/* | | `TurnCompletedNotification` | |
| `hook/completed` | undecided | missing | hook runs; hooks/list is Tier B | B | HookCompletedNotification (untyped) | |
| `turn/diff/updated` | A | missing | S3 projection: turn/* | | `TurnDiffUpdatedNotification` | |
| `turn/plan/updated` | A | missing | S3 projection: turn/* | | `TurnPlanUpdatedNotification` | |
| `item/started` | A | missing | S3 projection: item/* | | `ItemStartedNotification` | |
| `item/autoApprovalReview/started` | undecided | missing | Codex Guardian; Codewhale has its own Auto-Review posture | C | ItemGuardianApprovalReviewStartedNotification (untyped) | |
| `item/autoApprovalReview/completed` | undecided | missing | Codex Guardian; Codewhale has its own Auto-Review posture | C | ItemGuardianApprovalReviewCompletedNotification (untyped) | |
| `autoApprovalReview/strictReviewRequired` | undecided | missing | Codex Guardian; Codewhale has its own Auto-Review posture | C | StrictReviewRequiredNotification (untyped) | |
| `item/completed` | A | missing | S3 projection: item/* | | `ItemCompletedNotification` | |
| `rawResponseItem/completed` | C | never-sent | internal-only upstream (Codex Cloud, exact upstream usage); TypeScript-only in the export | | RawResponseItemCompletedNotification (untyped) | |
| `rawResponse/completed` | C | never-sent | internal-only upstream (Codex Cloud, exact upstream usage); TypeScript-only in the export | | RawResponseCompletedNotification (untyped) | |
| `item/agentMessage/delta` | A | missing | S3 projection: item/* | | `AgentMessageDeltaNotification` | |
| `item/plan/delta` | A | missing | S3 projection: item/* | | `PlanDeltaNotification` | |
| `command/exec/outputDelta` | B | missing | pairs with a Tier B service | | `CommandExecOutputDeltaNotification` | |
| `process/outputDelta` | undecided | missing | background processes | B | ProcessOutputDeltaNotification (untyped) | |
| `process/exited` | undecided | missing | background processes | B | ProcessExitedNotification (untyped) | |
| `item/commandExecution/outputDelta` | A | missing | S3 projection: item/* | | `CommandExecutionOutputDeltaNotification` | |
| `item/commandExecution/terminalInteraction` | A | missing | S3 projection: item/* | | `TerminalInteractionNotification` | |
| `item/fileChange/outputDelta` | A | missing | S3 projection: item/* | | `FileChangeOutputDeltaNotification` | |
| `item/fileChange/patchUpdated` | A | missing | S3 projection: item/* | | `FileChangePatchUpdatedNotification` | |
| `serverRequest/resolved` | A | missing | S6 interactions (CW-7704) | | `ServerRequestResolvedNotification` | |
| `item/mcpToolCall/progress` | A | missing | S3 projection: item/* | | `McpToolCallProgressNotification` | |
| `mcpServer/oauthLogin/completed` | undecided | missing | MCP server events | B | McpServerOauthLoginCompletedNotification (untyped) | |
| `mcpServer/startupStatus/updated` | undecided | missing | MCP server events | B | McpServerStatusUpdatedNotification (untyped) | |
| `mcpServer/event/stream/notification` | undecided | missing | MCP server events | B | McpServerEventStreamNotification (untyped) | |
| `account/updated` | undecided | missing | OpenAI account state | C | AccountUpdatedNotification (untyped) | |
| `account/gatewayOAuth/changed` | C | never-sent | pairs with a Tier C method | | GatewayOAuthChangedNotification (untyped) | |
| `account/rateLimits/updated` | undecided | missing | OpenAI account state | C | AccountRateLimitsUpdatedNotification (untyped) | |
| `app/list/updated` | C | never-sent | pairs with a Tier C method | | AppListUpdatedNotification (untyped) | |
| `remoteControl/status/changed` | undecided | missing | remote control status | C | RemoteControlStatusChangedNotification (untyped) | |
| `externalAgentConfig/import/progress` | undecided | missing | pairs with externalAgentConfig/import | C | ExternalAgentConfigImportProgressNotification (untyped) | |
| `externalAgentConfig/import/completed` | undecided | missing | pairs with externalAgentConfig/import | C | ExternalAgentConfigImportCompletedNotification (untyped) | |
| `fs/changed` | B | missing | pairs with a Tier B service | | `FsChangedNotification` | |
| `item/reasoning/summaryTextDelta` | A | missing | S3 projection: item/* | | `ReasoningSummaryTextDeltaNotification` | |
| `item/reasoning/summaryPartAdded` | A | missing | S3 projection: item/* | | `ReasoningSummaryPartAddedNotification` | |
| `item/reasoning/textDelta` | A | missing | S3 projection: item/* | | `ReasoningTextDeltaNotification` | |
| `thread/compacted` | B | missing | pairs with a Tier B service | | `ContextCompactedNotification` | |
| `model/rerouted` | undecided | missing | OpenAI model routing, verification, moderation and safety | C | ModelReroutedNotification (untyped) | |
| `model/verification` | undecided | missing | OpenAI model routing, verification, moderation and safety | C | ModelVerificationNotification (untyped) | |
| `modelProvider/authRecoveryStarted` | undecided | missing | provider auth recovery | B | AuthRecoveryNotification (untyped) | |
| `modelProvider/authRecoveryCompleted` | undecided | missing | provider auth recovery | B | AuthRecoveryNotification (untyped) | |
| `turn/moderationMetadata` | undecided | missing | OpenAI model routing, verification, moderation and safety | C | TurnModerationMetadataNotification (untyped) | |
| `model/safetyBuffering/updated` | undecided | missing | OpenAI model routing, verification, moderation and safety | C | ModelSafetyBufferingUpdatedNotification (untyped) | |
| `warning` | undecided | missing | generic warnings | A | WarningNotification (untyped) | |
| `guardianWarning` | undecided | missing | Codex Guardian; Codewhale has its own Auto-Review posture | C | GuardianWarningNotification (untyped) | |
| `deprecationNotice` | undecided | missing | generic warnings | A | DeprecationNoticeNotification (untyped) | |
| `configWarning` | undecided | missing | generic warnings | A | ConfigWarningNotification (untyped) | |
| `fuzzyFileSearch/sessionUpdated` | undecided | missing | pairs with fuzzyFileSearch | B | FuzzyFileSearchSessionUpdatedNotification (untyped) | |
| `fuzzyFileSearch/sessionCompleted` | undecided | missing | pairs with fuzzyFileSearch | B | FuzzyFileSearchSessionCompletedNotification (untyped) | |
| `thread/realtime/started` | undecided | missing | realtime audio sessions; no Codewhale equivalent | C | ThreadRealtimeStartedNotification (untyped) | |
| `thread/realtime/itemAdded` | undecided | missing | realtime audio sessions; no Codewhale equivalent | C | ThreadRealtimeItemAddedNotification (untyped) | |
| `thread/realtime/item/started` | undecided | missing | realtime audio sessions; no Codewhale equivalent | C | ThreadRealtimeItemStartedNotification (untyped) | |
| `thread/realtime/item/transcript/delta` | undecided | missing | realtime audio sessions; no Codewhale equivalent | C | ThreadRealtimeItemTranscriptDeltaNotification (untyped) | |
| `thread/realtime/item/completed` | undecided | missing | realtime audio sessions; no Codewhale equivalent | C | ThreadRealtimeItemCompletedNotification (untyped) | |
| `thread/realtime/transcript/delta` | undecided | missing | realtime audio sessions; no Codewhale equivalent | C | ThreadRealtimeTranscriptDeltaNotification (untyped) | |
| `thread/realtime/transcript/done` | undecided | missing | realtime audio sessions; no Codewhale equivalent | C | ThreadRealtimeTranscriptDoneNotification (untyped) | |
| `thread/realtime/outputAudio/delta` | undecided | missing | realtime audio sessions; no Codewhale equivalent | C | ThreadRealtimeOutputAudioDeltaNotification (untyped) | |
| `thread/realtime/sdp` | undecided | missing | realtime audio sessions; no Codewhale equivalent | C | ThreadRealtimeSdpNotification (untyped) | |
| `thread/realtime/error` | undecided | missing | realtime audio sessions; no Codewhale equivalent | C | ThreadRealtimeErrorNotification (untyped) | |
| `thread/realtime/closed` | undecided | missing | realtime audio sessions; no Codewhale equivalent | C | ThreadRealtimeClosedNotification (untyped) | |
| `windows/worldWritableWarning` | undecided | missing | Codex Windows sandbox | C | WindowsWorldWritableWarningNotification (untyped) | |
| `windowsSandbox/setupCompleted` | undecided | missing | Codex Windows sandbox | C | WindowsSandboxSetupCompletedNotification (untyped) | |
| `account/login/completed` | undecided | missing | OpenAI account state | C | AccountLoginCompletedNotification (untyped) | |
## Totals
| Kind | A | B | C | undecided |
| --- | --- | --- | --- | --- |
| `error` | A | missing | S3 projection: errors | |
| `thread/started` | A | missing | S3 projection: thread lifecycle | |
| `thread/status/changed` | A | missing | S3 projection: thread lifecycle | |
| `thread/readState/changed` | undecided | missing | thread state beyond the Tier A set | A |
| `thread/archived` | A | missing | S3 projection: thread lifecycle | |
| `thread/deleted` | B | missing | pairs with a Tier B service | |
| `thread/unarchived` | A | missing | S3 projection: thread lifecycle | |
| `thread/closed` | A | missing | S3 projection: thread lifecycle | |
| `thread/reverted` | B | missing | pairs with a Tier B service | |
| `skills/changed` | B | missing | pairs with a Tier B service | |
| `thread/name/updated` | A | missing | S3 projection: thread lifecycle | |
| `thread/attachment/updated` | undecided | missing | Codex desktop attachments, predictions, projects and environments | C |
| `thread/goal/updated` | A | missing | S3 projection with S8 goals | |
| `thread/prediction/updated` | undecided | missing | Codex desktop attachments, predictions, projects and environments | C |
| `thread/goal/cleared` | A | missing | S3 projection with S8 goals | |
| `thread/queue/changed` | undecided | missing | thread state beyond the Tier A set | A |
| `project/changed` | undecided | missing | Codex desktop attachments, predictions, projects and environments | C |
| `thread/project/updated` | undecided | missing | Codex desktop attachments, predictions, projects and environments | C |
| `thread/environment/connected` | undecided | missing | Codex desktop attachments, predictions, projects and environments | C |
| `thread/environment/disconnected` | undecided | missing | Codex desktop attachments, predictions, projects and environments | C |
| `thread/settings/updated` | undecided | missing | thread state beyond the Tier A set | A |
| `thread/tokenUsage/updated` | A | missing | S3 projection: thread lifecycle | |
| `turn/started` | A | missing | S3 projection: turn/* | |
| `hook/started` | undecided | missing | hook runs; hooks/list is Tier B | B |
| `turn/completed` | A | missing | S3 projection: turn/* | |
| `hook/completed` | undecided | missing | hook runs; hooks/list is Tier B | B |
| `turn/diff/updated` | A | missing | S3 projection: turn/* | |
| `turn/plan/updated` | A | missing | S3 projection: turn/* | |
| `item/started` | A | missing | S3 projection: item/* | |
| `item/autoApprovalReview/started` | undecided | missing | Codex Guardian; Codewhale has its own Auto-Review posture | C |
| `item/autoApprovalReview/completed` | undecided | missing | Codex Guardian; Codewhale has its own Auto-Review posture | C |
| `autoApprovalReview/strictReviewRequired` | undecided | missing | Codex Guardian; Codewhale has its own Auto-Review posture | C |
| `item/completed` | A | missing | S3 projection: item/* | |
| `rawResponseItem/completed` | C | never-sent | internal-only upstream (Codex Cloud, exact upstream usage); TypeScript-only in the export | |
| `rawResponse/completed` | C | never-sent | internal-only upstream (Codex Cloud, exact upstream usage); TypeScript-only in the export | |
| `item/agentMessage/delta` | A | missing | S3 projection: item/* | |
| `item/plan/delta` | A | missing | S3 projection: item/* | |
| `command/exec/outputDelta` | B | missing | pairs with a Tier B service | |
| `process/outputDelta` | undecided | missing | background processes | B |
| `process/exited` | undecided | missing | background processes | B |
| `item/commandExecution/outputDelta` | A | missing | S3 projection: item/* | |
| `item/commandExecution/terminalInteraction` | A | missing | S3 projection: item/* | |
| `item/fileChange/outputDelta` | A | missing | S3 projection: item/* | |
| `item/fileChange/patchUpdated` | A | missing | S3 projection: item/* | |
| `serverRequest/resolved` | A | missing | S6 interactions (CW-7704) | |
| `item/mcpToolCall/progress` | A | missing | S3 projection: item/* | |
| `mcpServer/oauthLogin/completed` | undecided | missing | MCP server events | B |
| `mcpServer/startupStatus/updated` | undecided | missing | MCP server events | B |
| `mcpServer/event/stream/notification` | undecided | missing | MCP server events | B |
| `account/updated` | undecided | missing | OpenAI account state | C |
| `account/gatewayOAuth/changed` | C | never-sent | pairs with a Tier C method | |
| `account/rateLimits/updated` | undecided | missing | OpenAI account state | C |
| `app/list/updated` | C | never-sent | pairs with a Tier C method | |
| `remoteControl/status/changed` | undecided | missing | remote control status | C |
| `externalAgentConfig/import/progress` | undecided | missing | pairs with externalAgentConfig/import | C |
| `externalAgentConfig/import/completed` | undecided | missing | pairs with externalAgentConfig/import | C |
| `fs/changed` | B | missing | pairs with a Tier B service | |
| `item/reasoning/summaryTextDelta` | A | missing | S3 projection: item/* | |
| `item/reasoning/summaryPartAdded` | A | missing | S3 projection: item/* | |
| `item/reasoning/textDelta` | A | missing | S3 projection: item/* | |
| `thread/compacted` | B | missing | pairs with a Tier B service | |
| `model/rerouted` | undecided | missing | OpenAI model routing, verification, moderation and safety | C |
| `model/verification` | undecided | missing | OpenAI model routing, verification, moderation and safety | C |
| `modelProvider/authRecoveryStarted` | undecided | missing | provider auth recovery | B |
| `modelProvider/authRecoveryCompleted` | undecided | missing | provider auth recovery | B |
| `turn/moderationMetadata` | undecided | missing | OpenAI model routing, verification, moderation and safety | C |
| `model/safetyBuffering/updated` | undecided | missing | OpenAI model routing, verification, moderation and safety | C |
| `warning` | undecided | missing | generic warnings | A |
| `guardianWarning` | undecided | missing | Codex Guardian; Codewhale has its own Auto-Review posture | C |
| `deprecationNotice` | undecided | missing | generic warnings | A |
| `configWarning` | undecided | missing | generic warnings | A |
| `fuzzyFileSearch/sessionUpdated` | undecided | missing | pairs with fuzzyFileSearch | B |
| `fuzzyFileSearch/sessionCompleted` | undecided | missing | pairs with fuzzyFileSearch | B |
| `thread/realtime/started` | undecided | missing | realtime audio sessions; no Codewhale equivalent | C |
| `thread/realtime/itemAdded` | undecided | missing | realtime audio sessions; no Codewhale equivalent | C |
| `thread/realtime/item/started` | undecided | missing | realtime audio sessions; no Codewhale equivalent | C |
| `thread/realtime/item/transcript/delta` | undecided | missing | realtime audio sessions; no Codewhale equivalent | C |
| `thread/realtime/item/completed` | undecided | missing | realtime audio sessions; no Codewhale equivalent | C |
| `thread/realtime/transcript/delta` | undecided | missing | realtime audio sessions; no Codewhale equivalent | C |
| `thread/realtime/transcript/done` | undecided | missing | realtime audio sessions; no Codewhale equivalent | C |
| `thread/realtime/outputAudio/delta` | undecided | missing | realtime audio sessions; no Codewhale equivalent | C |
| `thread/realtime/sdp` | undecided | missing | realtime audio sessions; no Codewhale equivalent | C |
| `thread/realtime/error` | undecided | missing | realtime audio sessions; no Codewhale equivalent | C |
| `thread/realtime/closed` | undecided | missing | realtime audio sessions; no Codewhale equivalent | C |
| `windows/worldWritableWarning` | undecided | missing | Codex Windows sandbox | C |
| `windowsSandbox/setupCompleted` | undecided | missing | Codex Windows sandbox | C |
| `account/login/completed` | undecided | missing | OpenAI account state | C |
| Client requests | 19 | 25 | 19 | 46 |
| Client notifications | 1 | 0 | 0 | 0 |
| Server requests | 5 | 0 | 4 | 1 |
| Server notifications | 27 | 6 | 4 | 50 |
Typed definitions generated into `src/v2.rs`: 319.
## Errors
Codex envelopes carry no `jsonrpc` member. ACP keeps its own JSON-RPC 2.0 envelope and is not this contract.
| Name | Code | Meaning |
| --- | --- | --- |
| `PARSE_ERROR` | -32700 | the line or message is not JSON |
| `INVALID_REQUEST` | -32600 | not a valid request envelope, or a request before initialize |
| `METHOD_NOT_FOUND` | -32601 | Tier C or undecided method, or a name the pinned export does not have |
| `INVALID_PARAMS` | -32602 | params do not decode into the selected method's typed params |
| `INTERNAL_ERROR` | -32603 | the Engine failed while serving a valid request |
| `OVERLOADED` | -32001 | upstream Codex transport overload code; the outgoing queue is full |
| `THREAD_NOT_FOUND` | -32004 | the named thread does not exist (existing App Server code) |
| `RUNTIME_UNAVAILABLE` | -32005 | the Engine could not be reached (existing App Server code) |
| `CAPABILITY_UNSUPPORTED` | -32020 | the method exists and is selected, but this thread's executor or placement cannot serve it; `data` is `CapabilityRefusalData`; the request has no side effect and never falls back to local execution |
## Codewhale extensions
These ride on Codex shapes without changing any pinned field. The pinned
`Thread` schema allows additional properties, so a Codex client ignores the
fields. Codewhale notifications live under `codewhale/`, because Codex already
uses `thread/environment/*` for its own environments.
These ride on Codex shapes without changing any pinned field. The pinned `Thread` schema allows additional properties, so a Codex client ignores the fields. Codewhale notifications live under `codewhale/`, because Codex already uses `thread/environment/*` for its own environments. Extension shapes are declared under `extensions.definitions` in the manifest and generated with the Codex types; none carries a credential.
| Name | Kind | Tier | Status | Shape |
| --- | --- | --- | --- | --- |
| `Thread.executor` | field | A | defined, unpopulated | `rpc::ThreadExecutor` `{ kind, agentId, agentName? }`: a thread driven by an external agent over ACP; absent for a native thread. Never an environment value or a credential. |
| `Thread.environment` | field | B | defined, unpopulated | `rpc::ThreadEnvironment` `{ id, providerKind }`: where the thread runs; absent means this computer. |
| `Thread.executor` | field | A | defined, unpopulated | `ThreadExecutor` `{ kind, agentId, agentName? }`: a thread driven by an external agent over ACP; absent for a native thread. Never an environment value or a credential. |
| `Thread.environment` | field | B | defined, unpopulated | `ThreadPlacement` `{ id, providerKind }` (named apart from Codex `ThreadEnvironment`, which is a different type): where the thread runs; absent means this computer. A thread placed in an environment this build cannot serve refuses with `CAPABILITY_UNSUPPORTED` (`environment_not_supported`) and never runs locally. |
| `codewhale/environment/*` | server notifications | B | reserved, not implemented | environment state changes for placement |
| `ThreadItem.placement` | field | B | reserved, not implemented | where a child or job ran |
| `approval.decided.responder` | Runtime journal field | A | implemented | the client that answered (`desktop`, `phone`, `web`, `tui`, `acp`), as declared by that client |
+19 -4
View File
@@ -26,10 +26,25 @@ The command writes `schema/json/runtime.schema.json`,
`--check` compares all three without writing. Normal runtime startup does not
generate files or enable schema machinery.
`src/methods.rs` holds the typed Tier A and B Codex method enums. `METHODS.md` is the
committed inventory of every pinned Codex method with its tier and status, and
`tests/methods.rs` checks the enums, the inventory and the pinned export against
each other.
`contract/codex-v2.json` is the frozen, versioned Codex v2 contract manifest:
every pinned stable Codex method with its tier (A, B, C or undecided), status,
basis and, for selected methods, the upstream variant, params and result types;
the dispatch rule for each tier; the error codes; and the Codewhale extensions.
`src/codegen` reads it with the unmodified pinned export and writes
`src/methods.rs`, `src/v2.rs`, `METHODS.md`,
`schema/json/codex-v2.schema.json` and `schema/typescript/codex-v2.d.ts`:
```sh
cargo run -p codewhale-app-server-protocol --bin codewhale-codex-contract -- --write
```
`--check` compares without writing, and `tests/contract.rs` fails on any drift,
on any disagreement between the manifest and the pinned method sets, and on
any selected shape whose typed Rust value does not round-trip an instance the
pinned schema accepts. `v2::ClientRequestParams`, `v2::ServerNotification`,
`v2::ServerRequestParams`, `v2::ClientResponse` and `v2::ServerRequestResponse`
are the typed envelopes; Codex envelopes carry no `jsonrpc` member. A typed
shape is not a handler: `METHODS.md` status says what main dispatches.
The selected future Codex wire target is official `rust-v0.166.0`, commit
`6ff1e1ca3b30fc8b914720681fcf5fe62e95f856`. Its four-variant envelope and initialize/initialized lifecycle serve JSONL
+2
View File
@@ -7,3 +7,5 @@ Changed files: these adaptations remove Codex-specific derive/export dependencie
The exact pinned wire golden fixtures under tests/pinned are unmodified members of the local stable precomputed schema export. This is a staged owner/lifecycle adoption, not complete wire/parity acceptance.
The method sets in tests/pinned/codex/{ClientRequest,ClientNotification,ServerRequest,ServerNotification}.ts are unmodified TypeScript members of the same commit's stable precomputed export (`codex-rs/app-server-protocol/schema/precomputed/app-server-exports-stable.json.zst`, compressed SHA256 `c3802deddc224896967bccd29e353e93e62af768b44dbd5d583e35825fb3e02a`). Member SHA256: ClientRequest.ts `d52eab2b624ee8bab88a121f78a5465faaa6a2701bbd0d2979dc7e5937741b36`, ClientNotification.ts `96692c191ff9ac32c2688b46a0a04f23d15646937adc352f2adcc8211c6d17a0`, ServerRequest.ts `30880f1a8da876eec30600ba27560d05ad0db5c645e14564c9c79a2429274f06`, ServerNotification.ts `df9b5ab2f11010ac336fb7c5b6bb386a7c389cd5dbe742ac5c804fcc69bd40d9`. The TypeScript members are used because they carry the full wire enums: the export's JSON schemas omit three deprecated v1 client requests and two internal-only notifications. Variant names in src/methods.rs follow the upstream `common.rs` variants.
The selected v2 contract is generated from two further unmodified pins of the same commit. `tests/pinned/codex/codex_app_server_protocol.schemas.json` is the `json_schema/codex_app_server_protocol.schemas.json` member of the stable precomputed export (expanded export SHA256 `7ae41cc257d2a4d9f93d1e99f24560802439d2f2d050ec5685832025ced962a2`; member SHA256 `9b6d53647b00d68a04181b826599025a810cfa78acb3ec4d518c68b4120499c0`). `tests/pinned/codex/common.rs` is `codex-rs/app-server-protocol/src/protocol/common.rs` (Git blob `2936c01855e463275d8306df8583ec0303e5c854`, SHA256 `b7f0a90dcc822962e59890b252a43142a113cb1355f333d8c6b0f704b97f89f7`); it is read only by `tests/contract.rs` to check each selected request's result type and is not compiled. The Rust types in `src/v2.rs`, the TypeScript in `schema/typescript/codex-v2.d.ts` and the selected-closure JSON Schema in `schema/json/codex-v2.schema.json` are derived from the schema member by `src/codegen`; they are Apache-2.0 contract adaptations like the files above.
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,410 @@
export type AbsolutePathBuf = string;
export type AdditionalFileSystemPermissions = { "entries"?: Array<FileSystemSandboxEntry> | null; "globScanMaxDepth"?: number | null; "read"?: Array<LegacyAppPathString> | null; "write"?: Array<LegacyAppPathString> | null };
export type AdditionalNetworkPermissions = { "enabled"?: boolean | null };
export type AgentMessageDelivery = "async";
export type AgentMessageDeltaNotification = { "delta": string; "itemId": string; "threadId": string; "turnId": string };
export type AgentPath = string;
export type AllowDenyRequirement = "allow" | "deny";
export type AnalyticsConfig = { "enabled"?: boolean | null; [key: string]: unknown };
export type ApprovalsReviewer = "user" | "auto_review" | "guardian_subagent";
export type AskForApproval = "untrusted" | "on-request" | "never" | { "granular": { "mcp_elicitations": boolean; "request_permissions"?: boolean; "rules": boolean; "sandbox_approval": boolean; "skill_approval"?: boolean } };
export type AsyncUserInputQuestion = { "options"?: Array<string> | null; "title": string };
export type AutoCompactTokenLimitScope = "total" | "body_after_prefix";
export type BrowserUseConfig = { "allow_history_access"?: boolean | null; "default_origin_policy"?: BrowserUseOriginPolicyConfig | null; "origins"?: { [key: string]: BrowserUseOriginPolicyConfig } | null };
export type BrowserUseOriginPolicyConfig = { "access"?: AllowDenyRequirement | null; "downloads"?: AllowDenyRequirement | null; "full_cdp_access"?: AllowDenyRequirement | null; "uploads"?: AllowDenyRequirement | null };
export type ByteRange = { "end": number; "start": number };
export type CapabilityRefusal = { "code": CapabilityRefusalCode; "method"?: string | null; "threadId"?: string | null };
export type CapabilityRefusalCode = "unknown_agent" | "not_installed" | "adapter_missing" | "below_min_version" | "needs_sign_in" | "no_acp" | "executor_not_supported" | "environment_not_supported" | "steer_not_supported" | "reopen_not_supported";
export type CapabilityRefusalData = { "codewhaleRefusal": CapabilityRefusal };
export type ClientInfo = { "name": string; "title"?: string | null; "version": string };
export type CodexErrorInfo = CodexErrorInfoVariant0 | CodexErrorInfoVariant1 | CodexErrorInfoVariant2 | CodexErrorInfoVariant3 | CodexErrorInfoVariant4 | CodexErrorInfoVariant5 | unknown;
export type CodexErrorInfoVariant0 = "contextWindowExceeded" | "sessionBudgetExceeded" | "usageLimitExceeded" | "rateLimitExceeded" | "flexUnavailable" | "serverOverloaded" | "modelNotFound" | "policyCapacityUnavailable" | "cyberPolicy" | "misalignmentPolicyViolation" | "tooManyDenials" | "internalServerError" | "unauthorized" | "badRequest" | "threadRollbackFailed" | "sandboxError" | "other";
export type CodexErrorInfoVariant1 = { "httpConnectionFailed": CodexErrorInfoVariant1HttpConnectionFailed };
export type CodexErrorInfoVariant1HttpConnectionFailed = { "httpStatusCode"?: number | null };
export type CodexErrorInfoVariant2 = { "responseStreamConnectionFailed": CodexErrorInfoVariant2ResponseStreamConnectionFailed };
export type CodexErrorInfoVariant2ResponseStreamConnectionFailed = { "httpStatusCode"?: number | null };
export type CodexErrorInfoVariant3 = { "responseStreamDisconnected": CodexErrorInfoVariant3ResponseStreamDisconnected };
export type CodexErrorInfoVariant3ResponseStreamDisconnected = { "httpStatusCode"?: number | null };
export type CodexErrorInfoVariant4 = { "responseTooManyFailedAttempts": CodexErrorInfoVariant4ResponseTooManyFailedAttempts };
export type CodexErrorInfoVariant4ResponseTooManyFailedAttempts = { "httpStatusCode"?: number | null };
export type CodexErrorInfoVariant5 = { "activeTurnNotSteerable": CodexErrorInfoVariant5ActiveTurnNotSteerable };
export type CodexErrorInfoVariant5ActiveTurnNotSteerable = { "turnKind": NonSteerableTurnKind };
export type CollabAgentState = { "message"?: string | null; "status": CollabAgentStatus };
export type CollabAgentStatus = "pendingInit" | "running" | "interrupted" | "completed" | "errored" | "shutdown" | "notFound";
export type CollabAgentTool = "spawnAgent" | "sendInput" | "resumeAgent" | "wait" | "closeAgent" | "sendMessage" | "followupTask" | "interruptAgent" | "listAgents";
export type CollabAgentToolCallStatus = "inProgress" | "completed" | "failed" | "interrupted";
export type CollaborationMode = { "mode": ModeKind; "settings": Settings };
export type CommandAction = { "command": string; "name": string; "path": LegacyAppPathString; "type": "read" } | { "command": string; "path"?: string | null; "type": "listFiles" } | { "command": string; "path"?: string | null; "query"?: string | null; "type": "search" } | { "command": string; "type": "unknown" };
export type CommandExecOutputDeltaNotification = { "capReached": boolean; "deltaBase64": string; "processId": string; "stream": CommandExecOutputStream };
export type CommandExecOutputStream = "stdout" | "stderr";
export type CommandExecParams = { "command": Array<string>; "cwd"?: string | null; "disableOutputCap"?: boolean; "disableTimeout"?: boolean; "env"?: { [key: string]: string | null } | null; "outputBytesCap"?: number | null; "processId"?: string | null; "sandboxPolicy"?: SandboxPolicy | null; "size"?: CommandExecTerminalSize | null; "streamStdin"?: boolean; "streamStdoutStderr"?: boolean; "timeoutMs"?: number | null; "tty"?: boolean };
export type CommandExecResizeParams = { "processId": string; "size": CommandExecTerminalSize };
export type CommandExecResizeResponse = Record<string, never>;
export type CommandExecResponse = { "exitCode": number; "stderr": string; "stdout": string };
export type CommandExecTerminalSize = { "cols": number; "rows": number };
export type CommandExecTerminateParams = { "processId": string };
export type CommandExecTerminateResponse = Record<string, never>;
export type CommandExecWriteParams = { "closeStdin"?: boolean; "deltaBase64"?: string | null; "processId": string };
export type CommandExecWriteResponse = Record<string, never>;
export type CommandExecutionApprovalDecision = "accept" | "acceptForSession" | { "acceptWithExecpolicyAmendment": { "execpolicy_amendment": Array<string> } } | { "applyNetworkPolicyAmendment": { "network_policy_amendment": NetworkPolicyAmendment } } | "decline" | "cancel";
export type CommandExecutionApprovalKind = "command" | "writeStdin";
export type CommandExecutionOutputDeltaNotification = { "delta": string; "itemId": string; "threadId": string; "turnId": string };
export type CommandExecutionRequestApprovalParams = { "approvalId"?: string | null; "command"?: string | null; "commandActions"?: Array<CommandAction> | null; "cwd"?: LegacyAppPathString | null; "environmentId"?: string | null; "itemId": string; "kind"?: CommandExecutionApprovalKind; "networkApprovalContext"?: NetworkApprovalContext | null; "proposedExecpolicyAmendment"?: Array<string> | null; "proposedNetworkPolicyAmendments"?: Array<NetworkPolicyAmendment> | null; "reason"?: string | null; "startedAtMs": number; "threadId": string; "turnId": string };
export type CommandExecutionRequestApprovalResponse = { "decision": CommandExecutionApprovalDecision };
export type CommandExecutionSource = "agent" | "userShell" | "unifiedExecStartup" | "unifiedExecInteraction";
export type CommandExecutionStatus = "inProgress" | "completed" | "failed" | "declined";
export type ComputerUseConfig = { "default_app_access"?: AllowDenyRequirement | null; "macos"?: ComputerUseMacosConfig | null; "windows"?: ComputerUseWindowsConfig | null };
export type ComputerUseMacosConfig = { "bundle_ids"?: { [key: string]: AllowDenyRequirement } | null };
export type ComputerUseWindowsConfig = { "aumids"?: { [key: string]: AllowDenyRequirement } | null; "exes"?: Array<ComputerUseWindowsExeConfig> | null };
export type ComputerUseWindowsExeConfig = { "access": AllowDenyRequirement; "binary_name"?: string | null; "product_name": string; "publisher_name": string };
export type Config = { "analytics"?: AnalyticsConfig | null; "approval_policy"?: AskForApproval | null; "approvals_reviewer"?: ApprovalsReviewer | null; "browser_use"?: BrowserUseConfig | null; "compact_prompt"?: string | null; "computer_use"?: ComputerUseConfig | null; "desktop"?: { [key: string]: unknown } | null; "developer_instructions"?: string | null; "forced_chatgpt_workspace_id"?: ForcedChatgptWorkspaceIds | null; "forced_login_method"?: ForcedLoginMethod | null; "instructions"?: string | null; "model"?: string | null; "model_auto_compact_token_limit"?: number | null; "model_auto_compact_token_limit_scope"?: AutoCompactTokenLimitScope | null; "model_context_window"?: number | null; "model_provider"?: string | null; "model_reasoning_effort"?: ReasoningEffort | null; "model_reasoning_summary"?: ReasoningSummary | null; "model_verbosity"?: Verbosity | null; "review_model"?: string | null; "sandbox_mode"?: SandboxMode | null; "sandbox_workspace_write"?: SandboxWorkspaceWrite | null; "service_tier"?: string | null; "tools"?: ToolsV2 | null; "web_search"?: WebSearchMode | null; [key: string]: unknown };
export type ConfigBatchWriteParams = { "edits": Array<ConfigEdit>; "expectedVersion"?: string | null; "filePath"?: string | null; "reloadUserConfig"?: boolean };
export type ConfigEdit = { "keyPath": string; "mergeStrategy": MergeStrategy; "value": unknown };
export type ConfigLayer = { "config": unknown; "disabledReason"?: string | null; "name": ConfigLayerSource; "version": string };
export type ConfigLayerMetadata = { "name": ConfigLayerSource; "version": string };
export type ConfigLayerSource = { "file": AbsolutePathBuf; "type": "packagedDefaults" } | { "domain": string; "key": string; "type": "mdm" } | { "file": AbsolutePathBuf; "type": "system" } | { "id": string; "name": string; "type": "enterpriseManaged" } | { "file": AbsolutePathBuf; "profile"?: string | null; "type": "user" } | { "dotCodexFolder": AbsolutePathBuf; "type": "project" } | { "type": "sessionFlags" } | { "file": AbsolutePathBuf; "type": "legacyManagedConfigTomlFromFile" } | { "type": "legacyManagedConfigTomlFromMdm" };
export type ConfigReadParams = { "cwd"?: string | null; "includeLayers"?: boolean };
export type ConfigReadResponse = { "config": Config; "layers"?: Array<ConfigLayer> | null; "origins": { [key: string]: ConfigLayerMetadata } };
export type ConfigValueWriteParams = { "expectedVersion"?: string | null; "filePath"?: string | null; "keyPath": string; "mergeStrategy": MergeStrategy; "value": unknown };
export type ConfigWriteResponse = { "filePath": AbsolutePathBuf; "overriddenMetadata"?: OverriddenMetadata | null; "status": WriteStatus; "version": string };
export type ContextCompactedNotification = { "threadId": string; "turnId": string };
export type CyberAccessProgram = "standard" | "daybreakBlue" | "daybreakRed";
export type DynamicToolCallOutputContentItem = { "text": string; "type": "inputText" } | { "imageUrl": string; "type": "inputImage" } | { "audioUrl": string; "type": "inputAudio" };
export type DynamicToolCallParams = { "arguments": unknown; "callId": string; "namespace"?: string | null; "threadId": string; "tool": string; "turnId": string };
export type DynamicToolCallResponse = { "contentItems": Array<DynamicToolCallOutputContentItem>; "success": boolean };
export type DynamicToolCallStatus = "inProgress" | "completed" | "failed";
export type ErrorNotification = { "error": TurnError; "threadId": string; "turnId": string; "willRetry": boolean };
export type FileChangeApprovalDecision = "accept" | "acceptForSession" | "decline" | "cancel";
export type FileChangeOutputDeltaNotification = { "delta": string; "itemId": string; "threadId": string; "turnId": string };
export type FileChangePatchUpdatedNotification = { "changes": Array<FileUpdateChange>; "itemId": string; "threadId": string; "turnId": string };
export type FileChangeRequestApprovalParams = { "grantRoot"?: string | null; "itemId": string; "reason"?: string | null; "startedAtMs": number; "threadId": string; "turnId": string };
export type FileChangeRequestApprovalResponse = { "decision": FileChangeApprovalDecision };
export type FileSystemAccessMode = "read" | "write" | "deny";
export type FileSystemPath = { "path": LegacyAppPathString; "type": "path" } | { "pattern": string; "type": "glob_pattern" } | { "value": FileSystemSpecialPath; "type": "special" };
export type FileSystemSandboxEntry = { "access": FileSystemAccessMode; "path": FileSystemPath };
export type FileSystemSpecialPath = { "kind": "root" } | { "kind": "minimal" } | { "subpath"?: LegacyAppPathString | null; "kind": "project_roots" } | { "kind": "tmpdir" } | { "kind": "slash_tmp" } | { "path": string; "subpath"?: LegacyAppPathString | null; "kind": "unknown" };
export type FileUpdateChange = { "diff": string; "kind": PatchChangeKind; "path": string };
export type ForcedChatgptWorkspaceIds = string | Array<string>;
export type ForcedLoginMethod = "chatgpt" | "api";
export type FsChangedNotification = { "changedPaths": Array<AbsolutePathBuf>; "watchId": string };
export type FsCopyParams = { "destinationPath": AbsolutePathBuf; "recursive"?: boolean; "sourcePath": AbsolutePathBuf };
export type FsCopyResponse = Record<string, never>;
export type FsCreateDirectoryParams = { "path": AbsolutePathBuf; "recursive"?: boolean | null };
export type FsCreateDirectoryResponse = Record<string, never>;
export type FsGetMetadataParams = { "path": AbsolutePathBuf };
export type FsGetMetadataResponse = { "createdAtMs": number; "isDirectory": boolean; "isFile": boolean; "isSymlink": boolean; "modifiedAtMs": number };
export type FsReadDirectoryEntry = { "fileName": string; "isDirectory": boolean; "isFile": boolean };
export type FsReadDirectoryParams = { "path": AbsolutePathBuf };
export type FsReadDirectoryResponse = { "entries": Array<FsReadDirectoryEntry> };
export type FsReadFileParams = { "path": AbsolutePathBuf };
export type FsReadFileResponse = { "dataBase64": string };
export type FsRemoveParams = { "force"?: boolean | null; "path": AbsolutePathBuf; "recursive"?: boolean | null };
export type FsRemoveResponse = Record<string, never>;
export type FsUnwatchParams = { "watchId": string };
export type FsUnwatchResponse = Record<string, never>;
export type FsWatchParams = { "path": AbsolutePathBuf; "watchId": string };
export type FsWatchResponse = { "path": AbsolutePathBuf };
export type FsWriteFileParams = { "dataBase64": string; "path": AbsolutePathBuf };
export type FsWriteFileResponse = Record<string, never>;
export type FunctionCallOutputBody = string | Array<FunctionCallOutputContentItem>;
export type FunctionCallOutputContentItem = { "text": string; "type": "input_text" } | { "detail"?: ImageDetail | null; "type": "input_image" } & FunctionCallOutputContentItemInputImageKind | { "audio_url": string; "type": "input_audio" } | { "encrypted_content": string; "type": "encrypted_content" };
export type FunctionCallOutputContentItemInputImageKind = { "image_url": string } | { "file_id": string };
export type GitInfo = { "branch"?: string | null; "originUrl"?: string | null; "sha"?: string | null };
export type GrantedPermissionProfile = { "fileSystem"?: AdditionalFileSystemPermissions | null; "network"?: AdditionalNetworkPermissions | null };
export type HookErrorInfo = { "message": string; "path": string };
export type HookEventName = "preModelRequest" | "postModelRequest" | "preToolUse" | "permissionRequest" | "postToolUse" | "preCompact" | "postCompact" | "sessionStart" | "sessionEnd" | "userPromptSubmit" | "subagentStart" | "subagentStop" | "stop" | "interrupt";
export type HookMetadata = { "additionalContextLimit"?: number | null; "currentHash": string; "displayOrder": number; "enabled": boolean; "environmentId"?: string | null; "eventName": HookEventName; "isManaged": boolean; "key": string; "matcher"?: string | null; "pluginId"?: string | null; "source": HookSource; "sourcePath": AbsolutePathBuf; "statusMessage"?: string | null; "timeoutSec": number; "trustStatus": HookTrustStatus } & HookMetadataKind;
export type HookMetadataKind = { "async"?: boolean; "command": string; "handlerType": "command" } | { "server": string; "tool": string; "handlerType": "mcpTool" } | { "handlerType": "prompt" } | { "handlerType": "agent" };
export type HookPromptFragment = { "hookRunId": string; "text": string };
export type HookSource = "system" | "user" | "project" | "mdm" | "sessionFlags" | "plugin" | "cloudRequirements" | "cloudManagedConfig" | "legacyManagedConfigFile" | "legacyManagedConfigMdm" | "unknown";
export type HookTrustStatus = "managed" | "untrusted" | "trusted" | "modified";
export type HooksListEntry = { "cwd": string; "errors": Array<HookErrorInfo>; "hooks": Array<HookMetadata>; "warnings": Array<string> };
export type HooksListParams = { "cwds"?: Array<string>; "threadId"?: string | null };
export type HooksListResponse = { "data": Array<HooksListEntry> };
export type ImageDetail = "auto" | "low" | "high" | "original";
export type ImageGenerationFailure = { "limitId": string; "resetsAt"?: number | null; "type": "usageLimitExceeded" };
export type ImageGenerationInputImage = { "path": AbsolutePathBuf; "sha256": string };
export type InitializeCapabilities = { "experimentalApi"?: boolean; "explicitGatewayOauth"?: boolean; "extensions"?: { [key: string]: unknown } | null; "mcpServerOpenaiFormElicitation"?: boolean; "optOutNotificationMethods"?: Array<string> | null; "requestAttestation"?: boolean };
export type InitializeParams = { "capabilities"?: InitializeCapabilities | null; "clientInfo": ClientInfo };
export type InitializeResponse = { "codexHome": AbsolutePathBuf; "platformFamily": string; "platformOs": string; "userAgent": string };
export type InputModality = "text" | "image" | "audio";
export type ItemCompletedNotification = { "completedAtMs": number; "item": ThreadItem; "threadId": string; "turnId": string };
export type ItemStartedNotification = { "item": ThreadItem; "startedAtMs": number; "threadId": string; "turnId": string };
export type LegacyAppPathString = string;
export type ListMcpServerStatusParams = { "cursor"?: string | null; "detail"?: McpServerStatusDetail | null; "limit"?: number | null; "serverName"?: string | null; "threadId"?: string | null };
export type ListMcpServerStatusResponse = { "data": Array<McpServerStatus>; "nextCursor"?: string | null };
export type McpAppDisplayMode = "inline" | "fullscreen";
export type McpAppUi = { "preferredModelDisplayMode": McpAppDisplayMode; "resourceUri": string };
export type McpAuthStatus = "unknown" | "unsupported" | "notLoggedIn" | "bearerToken" | "oAuth";
export type McpServerConnectionStatus = "notStarted" | "starting" | "connected" | "authenticationRequired" | "failed" | "cancelled" | "disabled";
export type McpServerInfo = { "description"?: string | null; "icons"?: Array<unknown> | null; "name": string; "title"?: string | null; "version": string; "websiteUrl"?: string | null };
export type McpServerStatus = { "authStatus": McpAuthStatus; "httpOrigin"?: string | null; "name": string; "pluginId"?: string | null; "resourceTemplates": Array<ResourceTemplate>; "resources": Array<Resource>; "runtimeStatus"?: McpServerConnectionStatus | null; "serverCapabilities"?: unknown; "serverInfo"?: McpServerInfo | null; "toolListMeta"?: unknown; "tools": { [key: string]: Tool }; "toolsError"?: string | null };
export type McpServerStatusDetail = "full" | "toolsAndAuthOnly";
export type McpToolCallAppContext = { "actionName"?: string | null; "appName"?: string | null; "connectorId": string; "linkId"?: string | null; "resourceUri"?: string | null };
export type McpToolCallError = { "message": string };
export type McpToolCallProgressNotification = { "itemId": string; "message": string; "threadId": string; "turnId": string };
export type McpToolCallResult = { "_meta"?: unknown; "content": Array<unknown>; "structuredContent"?: unknown };
export type McpToolCallStatus = "inProgress" | "completed" | "failed";
export type MemoryCitation = { "entries": Array<MemoryCitationEntry>; "threadIds": Array<string> };
export type MemoryCitationEntry = { "lineEnd": number; "lineStart": number; "note": string; "path": string };
export type MergeStrategy = "replace" | "upsert";
export type MessagePhase = "commentary" | "partial_answer" | "final_answer";
export type MisalignmentErrorDetails = { "detailedExplanation"?: string | null; "errorType"?: string | null; "reviewTarget"?: string | null; "steer"?: MisalignmentSteer | null };
export type MisalignmentSteer = { "message": string };
export type ModeKind = "plan" | "default";
export type Model = { "additionalSpeedTiers"?: Array<string>; "availabilityNux"?: ModelAvailabilityNux | null; "availableAccessPrograms"?: ModelAccessPrograms | null; "defaultReasoningEffort": ReasoningEffort; "defaultServiceTier"?: string | null; "description": string; "displayName": string; "hidden": boolean; "id": string; "inputModalities"?: Array<InputModality>; "isDefault": boolean; "model": string; "modelSpecialty"?: string | null; "multiAgentVersion"?: MultiAgentVersion | null; "serviceTiers"?: Array<ModelServiceTier>; "supportedReasoningEfforts": Array<ReasoningEffortOption>; "supportsPersonality"?: boolean; "upgrade"?: string | null; "upgradeInfo"?: ModelUpgradeInfo | null };
export type ModelAccessPrograms = { "cyber": Array<CyberAccessProgram> };
export type ModelAvailabilityNux = { "message": string };
export type ModelListParams = { "cursor"?: string | null; "includeHidden"?: boolean | null; "limit"?: number | null };
export type ModelListResponse = { "data": Array<Model>; "nextCursor"?: string | null };
export type ModelServiceTier = { "description": string; "id": string; "name": string };
export type ModelUpgradeInfo = { "migrationMarkdown"?: string | null; "model": string; "modelLink"?: string | null; "retirementAt"?: number | null; "upgradeCopy"?: string | null };
export type MultiAgentVersion = "disabled" | "v1" | "v2";
export type NetworkAccess = "restricted" | "enabled";
export type NetworkApprovalContext = { "host": string; "protocol": NetworkApprovalProtocol };
export type NetworkApprovalProtocol = "http" | "https" | "socks5Tcp" | "socks5Udp";
export type NetworkPolicyAmendment = { "action": NetworkPolicyRuleAction; "host": string };
export type NetworkPolicyRuleAction = "allow" | "deny";
export type NonSteerableTurnKind = "review" | "compact";
export type OverriddenMetadata = { "effectiveValue": unknown; "message": string; "overridingLayer": ConfigLayerMetadata };
export type PatchApplyStatus = "inProgress" | "completed" | "failed" | "declined";
export type PatchChangeKind = { "type": "add" } | { "type": "delete" } | { "move_path"?: string | null; "type": "update" };
export type PermissionGrantScope = "turn" | "session";
export type PermissionsRequestApprovalParams = { "cwd": LegacyAppPathString; "environmentId"?: string | null; "itemId": string; "permissions": RequestPermissionProfile; "reason"?: string | null; "startedAtMs": number; "threadId": string; "turnId": string };
export type PermissionsRequestApprovalResponse = { "permissions": GrantedPermissionProfile; "scope"?: PermissionGrantScope; "strictAutoReview"?: boolean | null };
export type Personality = "none" | "friendly" | "pragmatic";
export type PlanDeltaNotification = { "delta": string; "itemId": string; "threadId": string; "turnId": string };
export type ReasoningEffort = string;
export type ReasoningEffortOption = { "description": string; "reasoningEffort": ReasoningEffort };
export type ReasoningSummary = "auto" | "concise" | "detailed" | "none";
export type ReasoningSummaryPartAddedNotification = { "itemId": string; "summaryIndex": number; "threadId": string; "turnId": string };
export type ReasoningSummaryTextDeltaNotification = { "delta": string; "itemId": string; "summaryIndex": number; "threadId": string; "turnId": string };
export type ReasoningTextDeltaNotification = { "contentIndex": number; "delta": string; "itemId": string; "threadId": string; "turnId": string };
export type RequestId = string | number;
export type RequestPermissionProfile = { "fileSystem"?: AdditionalFileSystemPermissions | null; "network"?: AdditionalNetworkPermissions | null };
export type Resource = { "_meta"?: unknown; "annotations"?: unknown; "description"?: string | null; "icons"?: Array<unknown> | null; "mimeType"?: string | null; "name": string; "size"?: number | null; "title"?: string | null; "uri": string };
export type ResourceTemplate = { "annotations"?: unknown; "description"?: string | null; "mimeType"?: string | null; "name": string; "title"?: string | null; "uriTemplate": string };
export type ReviewDelivery = "inline";
export type ReviewStartParams = { "delivery"?: ReviewDelivery | null; "target": ReviewTarget; "threadId": string };
export type ReviewStartResponse = { "reviewThreadId": string; "turn": Turn };
export type ReviewTarget = { "type": "uncommittedChanges" } | { "branch": string; "type": "baseBranch" } | { "sha": string; "title"?: string | null; "type": "commit" } | { "instructions": string; "type": "custom" };
export type SandboxMode = "read-only" | "workspace-write" | "danger-full-access";
export type SandboxPolicy = { "type": "dangerFullAccess" } | { "networkAccess"?: boolean; "type": "readOnly" } | { "networkAccess"?: NetworkAccess; "type": "externalSandbox" } | { "excludeSlashTmp"?: boolean; "excludeTmpdirEnvVar"?: boolean; "networkAccess"?: boolean; "writableRoots"?: Array<AbsolutePathBuf>; "type": "workspaceWrite" };
export type SandboxWorkspaceWrite = { "exclude_slash_tmp"?: boolean; "exclude_tmpdir_env_var"?: boolean; "network_access"?: boolean; "writable_roots"?: Array<string> };
export type ServerRequestResolvedNotification = { "requestId": RequestId; "threadId": string };
export type SessionSource = "cli" | "vscode" | "exec" | "appServer" | "unknown" | { "custom": string } | { "subAgent": SubAgentSource };
export type Settings = { "developer_instructions"?: string | null; "model": string; "reasoning_effort"?: ReasoningEffort | null };
export type SkillDependencies = { "tools": Array<SkillToolDependency> };
export type SkillErrorInfo = { "message": string; "path": string };
export type SkillInterface = { "brandColor"?: string | null; "defaultPrompt"?: string | null; "displayName"?: string | null; "iconLarge"?: AbsolutePathBuf | null; "iconLargeUrl"?: string | null; "iconSmall"?: AbsolutePathBuf | null; "iconSmallUrl"?: string | null; "shortDescription"?: string | null };
export type SkillMetadata = { "dependencies"?: SkillDependencies | null; "description": string; "enabled": boolean; "interface"?: SkillInterface | null; "name": string; "path": LegacyAppPathString; "pluginId"?: string | null; "scope": SkillScope; "shortDescription"?: string | null };
export type SkillScope = "user" | "repo" | "system" | "admin";
export type SkillToolDependency = { "command"?: string | null; "description"?: string | null; "transport"?: string | null; "type": string; "url"?: string | null; "value": string };
export type SkillsChangedNotification = Record<string, never>;
export type SkillsListEntry = { "cwd": string; "errors": Array<SkillErrorInfo>; "skills": Array<SkillMetadata> };
export type SkillsListParams = { "cwds"?: Array<string>; "forceReload"?: boolean };
export type SkillsListResponse = { "data": Array<SkillsListEntry> };
export type SortDirection = "asc" | "desc";
export type SubAgentActivityKind = "started" | "interacted" | "interrupted" | "completed";
export type SubAgentSource = "review" | "compact" | "memory_consolidation" | { "thread_spawn": { "agent_nickname"?: string | null; "agent_path"?: AgentPath | null; "agent_role"?: string | null; "depth": number; "parent_thread_id": ThreadId } } | { "other": string };
export type TerminalInteractionNotification = { "itemId": string; "processId": string; "stdin": string; "threadId": string; "turnId": string };
export type TextElement = { "byteRange": ByteRange; "placeholder"?: string | null };
export type Thread = { "agentNickname"?: string | null; "agentRole"?: string | null; "cliVersion": string; "createdAt": number; "cwd": AbsolutePathBuf; "ephemeral": boolean; "forkedFromId"?: string | null; "gitInfo"?: GitInfo | null; "historyMode"?: ThreadHistoryMode; "id": string; "model"?: string | null; "modelProvider": string; "name"?: string | null; "originator"?: string | null; "parentThreadId"?: string | null; "path"?: string | null; "preview": string; "projectId": string | null; "reasoningEffort"?: ReasoningEffort | null; "recencyAt"?: number | null; "section"?: ThreadSection | null; "sectionEnteredAt"?: number | null; "sessionId": string; "source": SessionSource; "status": ThreadStatus; "threadSource"?: ThreadSource | null; "turns": Array<Turn>; "updatedAt": number; "executor"?: ThreadExecutor | null; "environment"?: ThreadPlacement | null };
export type ThreadActiveFlag = "waitingOnApproval" | "waitingOnUserInput";
export type ThreadArchiveParams = { "threadId": string };
export type ThreadArchiveResponse = Record<string, never>;
export type ThreadArchivedNotification = { "threadId": string };
export type ThreadClosedNotification = { "threadId": string };
export type ThreadCompactStartParams = { "threadId": string };
export type ThreadCompactStartResponse = Record<string, never>;
export type ThreadDeleteParams = { "threadId": string };
export type ThreadDeleteResponse = Record<string, never>;
export type ThreadDeletedNotification = { "threadId": string };
export type ThreadExecutor = { "kind": string; "agentId": string; "agentName"?: string | null };
export type ThreadForkParams = { "approvalPolicy"?: AskForApproval | null; "approvalsReviewer"?: ApprovalsReviewer | null; "baseInstructions"?: string | null; "config"?: { [key: string]: unknown } | null; "cwd"?: string | null; "developerInstructions"?: string | null; "ephemeral"?: boolean; "excludeTurns"?: boolean; "lastTurnId"?: string | null; "model"?: string | null; "modelProvider"?: string | null; "sandbox"?: SandboxMode | null; "serviceTier"?: string | null; "threadId": string; "threadSource"?: ThreadSource | null };
export type ThreadForkResponse = { "approvalPolicy": AskForApproval; "approvalsReviewer": ApprovalsReviewer; "cwd": AbsolutePathBuf; "disabledPluginIds"?: Array<string>; "instructionSources"?: Array<LegacyAppPathString>; "model": string; "modelProvider": string; "reasoningEffort"?: ReasoningEffort | null; "sandbox": SandboxPolicy; "serviceTier"?: string | null; "thread": Thread };
export type ThreadGoal = { "createdAt": number; "objective": string; "status": ThreadGoalStatus; "threadId": string; "timeUsedSeconds": number; "tokenBudget"?: number | null; "tokensUsed": number; "updatedAt": number };
export type ThreadGoalClearParams = { "origin"?: ThreadGoalMutationOrigin | null; "threadId": string };
export type ThreadGoalClearResponse = { "cleared": boolean };
export type ThreadGoalClearedNotification = { "threadId": string };
export type ThreadGoalGetParams = { "threadId": string };
export type ThreadGoalGetResponse = { "goal"?: ThreadGoal | null };
export type ThreadGoalMutationOrigin = "user" | "automatic";
export type ThreadGoalSetParams = { "objective"?: string | null; "origin"?: ThreadGoalMutationOrigin | null; "status"?: ThreadGoalStatus | null; "threadId": string; "tokenBudget"?: number | null };
export type ThreadGoalSetResponse = { "goal": ThreadGoal };
export type ThreadGoalStatus = "active" | "paused" | "blocked" | "usageLimited" | "budgetLimited" | "complete";
export type ThreadGoalUpdatedNotification = { "goal": ThreadGoal; "threadId": string; "turnId"?: string | null };
export type ThreadHistoryMode = "legacy" | "paginated";
export type ThreadId = string;
export type ThreadItem = { "completedAtMs"?: number | null; "startedAtMs"?: number | null } & ThreadItemKind;
export type ThreadItemEntry = { "completedAtMs"?: number | null; "item": ThreadItem; "startedAtMs"?: number | null; "turnId": string };
export type ThreadItemKind = { "clientId"?: string | null; "content": Array<UserInput>; "id": string; "type": "userMessage" } | { "fragments": Array<HookPromptFragment>; "id": string; "type": "hookPrompt" } | { "delivery"?: AgentMessageDelivery | null; "id": string; "memoryCitation"?: MemoryCitation | null; "phase"?: MessagePhase | null; "questions"?: Array<AsyncUserInputQuestion> | null; "text": string; "type": "agentMessage" } | { "id": string; "name": string; "namespace"?: string | null; "output": FunctionCallOutputBody; "type": "functionCallOutput" } | { "id": string; "text": string; "type": "plan" } | { "content"?: Array<string>; "id": string; "summary"?: Array<string>; "type": "reasoning" } | { "aggregatedOutput"?: string | null; "command": string; "commandActions": Array<CommandAction>; "cwd": LegacyAppPathString; "durationMs"?: number | null; "exitCode"?: number | null; "id": string; "pluginId"?: string | null; "processId"?: string | null; "scriptPath"?: string | null; "source"?: CommandExecutionSource; "status": CommandExecutionStatus; "type": "commandExecution" } | { "changes": Array<FileUpdateChange>; "id": string; "status": PatchApplyStatus; "type": "fileChange" } | { "appContext"?: McpToolCallAppContext | null; "arguments": unknown; "durationMs"?: number | null; "error"?: McpToolCallError | null; "id": string; "mcpAppResourceUri"?: string | null; "mcpAppUi"?: McpAppUi | null; "pluginId"?: string | null; "readOnlyHint"?: boolean | null; "result"?: McpToolCallResult | null; "server": string; "status": McpToolCallStatus; "tool": string; "type": "mcpToolCall" } | { "arguments": unknown; "contentItems"?: Array<DynamicToolCallOutputContentItem> | null; "durationMs"?: number | null; "id": string; "namespace"?: string | null; "status": DynamicToolCallStatus; "success"?: boolean | null; "tool": string; "type": "dynamicToolCall" } | { "agentsStates": { [key: string]: CollabAgentState }; "id": string; "model"?: string | null; "prompt"?: string | null; "reasoningEffort"?: ReasoningEffort | null; "receiverThreadIds": Array<string>; "senderThreadId": string; "status": CollabAgentToolCallStatus; "tool": CollabAgentTool; "type": "collabAgentToolCall" } | { "agentPath": string; "agentThreadId": string; "id": string; "kind": SubAgentActivityKind; "model"?: string | null; "reasoningEffort"?: ReasoningEffort | null; "type": "subAgentActivity" } | { "action"?: WebSearchAction | null; "id": string; "query": string; "results"?: Array<unknown> | null; "type": "webSearch" } | { "id": string; "path": LegacyAppPathString; "type": "imageView" } | { "durationMs": number; "id": string; "type": "sleep" } | { "failure"?: ImageGenerationFailure | null; "generationId"?: string | null; "id": string; "inputImages"?: Array<ImageGenerationInputImage>; "revisedPrompt"?: string | null; "savedPath"?: AbsolutePathBuf | null; "status": string; "transparentBackground"?: boolean | null; "type": "imageGeneration" } & ThreadItemKindImageGenerationKind | { "id": string; "review": string; "type": "enteredReviewMode" } | { "id": string; "review": string; "type": "exitedReviewMode" } | { "id": string; "type": "contextCompaction" };
export type ThreadItemKindImageGenerationKind = { "fileId": string } | { "result": string };
export type ThreadItemsListAnchor = { "itemId": string; "type": "item" };
export type ThreadItemsListCursor = string | ThreadItemsListAnchor;
export type ThreadItemsListParams = { "cursor"?: ThreadItemsListCursor | null; "limit"?: number | null; "sortDirection"?: SortDirection | null; "threadId": string; "turnId"?: string | null };
export type ThreadItemsListResponse = { "backwardsCursor"?: string | null; "data": Array<ThreadItemEntry>; "nextCursor"?: string | null };
export type ThreadListCwdFilter = string | Array<string>;
export type ThreadListParams = { "archived"?: boolean | null; "cursor"?: string | null; "cwd"?: ThreadListCwdFilter | null; "excludedThreadIds"?: Array<string> | null; "limit"?: number | null; "modelProviders"?: Array<string> | null; "originators"?: Array<string> | null; "searchTerm"?: string | null; "sectionId"?: string | null; "sortDirection"?: SortDirection | null; "sortKey"?: ThreadSortKey | null; "sourceKinds"?: Array<ThreadSourceKind> | null; "useStateDbOnly"?: boolean };
export type ThreadListResponse = { "backwardsCursor"?: string | null; "data": Array<Thread>; "nextCursor"?: string | null };
export type ThreadLoadedListParams = { "cursor"?: string | null; "limit"?: number | null };
export type ThreadLoadedListResponse = { "data": Array<string>; "nextCursor"?: string | null };
export type ThreadNameUpdatedNotification = { "threadId": string; "threadName"?: string | null };
export type ThreadPlacement = { "id": string; "providerKind": string };
export type ThreadReadParams = { "includeTurns"?: boolean; "threadId": string };
export type ThreadReadResponse = { "thread": Thread };
export type ThreadResumeParams = { "approvalPolicy"?: AskForApproval | null; "approvalsReviewer"?: ApprovalsReviewer | null; "baseInstructions"?: string | null; "config"?: { [key: string]: unknown } | null; "cwd"?: string | null; "developerInstructions"?: string | null; "excludeTurns"?: boolean; "model"?: string | null; "modelProvider"?: string | null; "personality"?: Personality | null; "sandbox"?: SandboxMode | null; "serviceTier"?: string | null; "threadId": string };
export type ThreadResumeResponse = { "approvalPolicy": AskForApproval; "approvalsReviewer": ApprovalsReviewer; "collaborationMode"?: CollaborationMode | null; "cwd": AbsolutePathBuf; "disabledPluginIds"?: Array<string>; "instructionSources"?: Array<LegacyAppPathString>; "itemsBackwardsCursor"?: string | null; "model": string; "modelProvider": string; "reasoningEffort"?: ReasoningEffort | null; "sandbox": SandboxPolicy; "serviceTier"?: string | null; "thread": Thread; "turnsBackwardsCursor"?: string | null };
export type ThreadRevertParams = { "beforeTurnId": string; "threadId": string };
export type ThreadRevertResponse = { "itemsBackwardsCursor"?: string | null; "thread": Thread; "turnsBackwardsCursor"?: string | null };
export type ThreadRevertedNotification = { "threadId": string };
export type ThreadSection = { "appearance"?: ThreadSectionAppearance | null; "id": string; "name": string };
export type ThreadSectionAppearance = { "color"?: string | null; "icon"?: string | null };
export type ThreadSetNameParams = { "name": string; "threadId": string };
export type ThreadSetNameResponse = Record<string, never>;
export type ThreadShellCommandParams = { "command": string; "threadId": string; "timeoutMs"?: number | null };
export type ThreadShellCommandResponse = Record<string, never>;
export type ThreadSortKey = "created_at" | "updated_at" | "recency_at" | "section_position";
export type ThreadSource = string;
export type ThreadSourceKind = "cli" | "vscode" | "exec" | "appServer" | "subAgent" | "subAgentReview" | "subAgentCompact" | "subAgentThreadSpawn" | "subAgentOther" | "unknown";
export type ThreadStartParams = { "approvalPolicy"?: AskForApproval | null; "approvalsReviewer"?: ApprovalsReviewer | null; "baseInstructions"?: string | null; "config"?: { [key: string]: unknown } | null; "cwd"?: string | null; "developerInstructions"?: string | null; "ephemeral"?: boolean | null; "model"?: string | null; "modelProvider"?: string | null; "personality"?: Personality | null; "sandbox"?: SandboxMode | null; "serviceName"?: string | null; "serviceTier"?: string | null; "sessionStartSource"?: ThreadStartSource | null; "threadSource"?: ThreadSource | null };
export type ThreadStartResponse = { "approvalPolicy": AskForApproval; "approvalsReviewer": ApprovalsReviewer; "cwd": AbsolutePathBuf; "disabledPluginIds"?: Array<string>; "instructionSources"?: Array<LegacyAppPathString>; "model": string; "modelProvider": string; "reasoningEffort"?: ReasoningEffort | null; "sandbox": SandboxPolicy; "serviceTier"?: string | null; "thread": Thread };
export type ThreadStartSource = "startup" | "clear";
export type ThreadStartedNotification = { "thread": Thread };
export type ThreadStatus = { "type": "notLoaded" } | { "type": "idle" } | { "type": "systemError" } | { "activeFlags": Array<ThreadActiveFlag>; "type": "active" };
export type ThreadStatusChangedNotification = { "status": ThreadStatus; "threadId": string };
export type ThreadTokenUsage = { "last": TokenUsageBreakdown; "modelContextWindow"?: number | null; "total": TokenUsageBreakdown };
export type ThreadTokenUsageUpdatedNotification = { "threadId": string; "tokenUsage": ThreadTokenUsage; "turnId": string };
export type ThreadTurnsListParams = { "cursor"?: string | null; "itemsView"?: TurnItemsView | null; "limit"?: number | null; "sortDirection"?: SortDirection | null; "threadId": string };
export type ThreadTurnsListResponse = { "backwardsCursor"?: string | null; "data": Array<Turn>; "nextCursor"?: string | null };
export type ThreadUnarchiveParams = { "threadId": string };
export type ThreadUnarchiveResponse = { "thread": Thread };
export type ThreadUnarchivedNotification = { "threadId": string };
export type ThreadUnsubscribeParams = { "threadId": string };
export type ThreadUnsubscribeResponse = { "status": ThreadUnsubscribeStatus };
export type ThreadUnsubscribeStatus = "notLoaded" | "notSubscribed" | "unsubscribed";
export type TokenUsageBreakdown = { "cacheWriteInputTokens"?: number; "cachedInputTokens": number; "inputTokens": number; "outputTokens": number; "reasoningOutputTokens": number; "totalTokens": number };
export type Tool = { "_meta"?: unknown; "annotations"?: unknown; "description"?: string | null; "icons"?: Array<unknown> | null; "inputSchema": unknown; "name": string; "outputSchema"?: unknown; "title"?: string | null };
export type ToolRequestUserInputAnswer = { "answers": Array<string> };
export type ToolRequestUserInputOption = { "description": string; "label": string };
export type ToolRequestUserInputParams = { "autoResolutionMs"?: number | null; "isBlocking": boolean; "itemId": string; "questions": Array<ToolRequestUserInputQuestion>; "threadId": string; "turnId": string };
export type ToolRequestUserInputQuestion = { "header": string; "id": string; "isOther"?: boolean; "isSecret"?: boolean; "options"?: Array<ToolRequestUserInputOption> | null; "question": string };
export type ToolRequestUserInputResponse = { "answers": { [key: string]: ToolRequestUserInputAnswer } };
export type ToolsV2 = { "web_search"?: WebSearchToolConfig | null };
export type Turn = { "completedAt"?: number | null; "durationMs"?: number | null; "error"?: TurnError | null; "id": string; "items": Array<ThreadItem>; "itemsView"?: TurnItemsView; "rootTurnId"?: string | null; "startedAt"?: number | null; "status": TurnStatus };
export type TurnCompletedNotification = { "threadId": string; "turn": Turn };
export type TurnDiffUpdatedNotification = { "diff": string; "threadId": string; "turnId": string };
export type TurnError = { "additionalDetails"?: string | null; "codexErrorInfo"?: CodexErrorInfo | null; "message": string; "misalignment"?: MisalignmentErrorDetails | null };
export type TurnInterruptParams = { "threadId": string; "turnId": string };
export type TurnInterruptResponse = Record<string, never>;
export type TurnItemsView = "notLoaded" | "summary" | "full";
export type TurnPlanStep = { "status": TurnPlanStepStatus; "step": string };
export type TurnPlanStepStatus = "pending" | "inProgress" | "completed";
export type TurnPlanUpdatedNotification = { "explanation"?: string | null; "plan": Array<TurnPlanStep>; "threadId": string; "turnId": string };
export type TurnStartParams = { "approvalPolicy"?: AskForApproval | null; "approvalsReviewer"?: ApprovalsReviewer | null; "clientUserMessageId"?: string | null; "cwd"?: string | null; "disabledPluginIds"?: Array<string> | null; "effort"?: ReasoningEffort | null; "input": Array<UserInput>; "model"?: string | null; "outputSchema"?: unknown; "parentTurnId"?: string | null; "personality"?: Personality | null; "rootTurnId"?: string | null; "sandboxPolicy"?: SandboxPolicy | null; "serviceTier"?: string | null; "serviceTierForTurn"?: string | null; "summary"?: ReasoningSummary | null; "threadId": string; "toolOutput"?: TurnToolOutput | null; "turnTrigger"?: string | null };
export type TurnStartResponse = { "turn": Turn };
export type TurnStartedNotification = { "threadId": string; "turn": Turn };
export type TurnStatus = "completed" | "interrupted" | "failed" | "inProgress";
export type TurnSteerParams = { "clientUserMessageId"?: string | null; "expectedTurnId": string; "input": Array<UserInput>; "threadId": string };
export type TurnSteerResponse = { "turnId": string };
export type TurnToolOutput = { "name": string; "namespace"?: string | null; "output": FunctionCallOutputBody; "retain"?: boolean };
export type UserInput = { "text": string; "text_elements"?: Array<TextElement>; "type": "text" } | { "detail"?: ImageDetail | null; "type": "image" } & UserInputImageKind | { "detail"?: ImageDetail | null; "path": string; "type": "localImage" } | { "url": string; "type": "audio" } | { "path": string; "type": "localAudio" } | { "name": string; "path": string; "type": "skill" } | { "name": string; "path": string; "type": "mention" };
export type UserInputImageKind = { "url": string } | { "fileId": string };
export type Verbosity = "low" | "medium" | "high";
export type WebSearchAction = { "queries"?: Array<string> | null; "query"?: string | null; "type": "search" } | { "url"?: string | null; "type": "openPage" } | { "pattern"?: string | null; "url"?: string | null; "type": "findInPage" } | { "type": "other" };
export type WebSearchContextSize = "low" | "medium" | "high";
export type WebSearchLocation = { "city"?: string | null; "country"?: string | null; "region"?: string | null; "timezone"?: string | null };
export type WebSearchMode = "disabled" | "cached" | "indexed" | "live";
export type WebSearchToolConfig = { "allowed_domains"?: Array<string> | null; "context_size"?: WebSearchContextSize | null; "location"?: WebSearchLocation | null };
export type WriteStatus = "ok" | "okOverridden";
export type ClientRequestMap = {
"initialize": { params: InitializeParams; result: InitializeResponse };
"thread/start": { params: ThreadStartParams; result: ThreadStartResponse };
"thread/resume": { params: ThreadResumeParams; result: ThreadResumeResponse };
"thread/fork": { params: ThreadForkParams; result: ThreadForkResponse };
"thread/archive": { params: ThreadArchiveParams; result: ThreadArchiveResponse };
"thread/delete": { params: ThreadDeleteParams; result: ThreadDeleteResponse };
"thread/unsubscribe": { params: ThreadUnsubscribeParams; result: ThreadUnsubscribeResponse };
"thread/name/set": { params: ThreadSetNameParams; result: ThreadSetNameResponse };
"thread/goal/set": { params: ThreadGoalSetParams; result: ThreadGoalSetResponse };
"thread/goal/get": { params: ThreadGoalGetParams; result: ThreadGoalGetResponse };
"thread/goal/clear": { params: ThreadGoalClearParams; result: ThreadGoalClearResponse };
"thread/unarchive": { params: ThreadUnarchiveParams; result: ThreadUnarchiveResponse };
"thread/compact/start": { params: ThreadCompactStartParams; result: ThreadCompactStartResponse };
"thread/shellCommand": { params: ThreadShellCommandParams; result: ThreadShellCommandResponse };
"thread/revert": { params: ThreadRevertParams; result: ThreadRevertResponse };
"thread/list": { params: ThreadListParams; result: ThreadListResponse };
"thread/loaded/list": { params: ThreadLoadedListParams; result: ThreadLoadedListResponse };
"thread/read": { params: ThreadReadParams; result: ThreadReadResponse };
"thread/turns/list": { params: ThreadTurnsListParams; result: ThreadTurnsListResponse };
"thread/items/list": { params: ThreadItemsListParams; result: ThreadItemsListResponse };
"skills/list": { params: SkillsListParams; result: SkillsListResponse };
"hooks/list": { params: HooksListParams; result: HooksListResponse };
"fs/readFile": { params: FsReadFileParams; result: FsReadFileResponse };
"fs/writeFile": { params: FsWriteFileParams; result: FsWriteFileResponse };
"fs/createDirectory": { params: FsCreateDirectoryParams; result: FsCreateDirectoryResponse };
"fs/getMetadata": { params: FsGetMetadataParams; result: FsGetMetadataResponse };
"fs/readDirectory": { params: FsReadDirectoryParams; result: FsReadDirectoryResponse };
"fs/remove": { params: FsRemoveParams; result: FsRemoveResponse };
"fs/copy": { params: FsCopyParams; result: FsCopyResponse };
"fs/watch": { params: FsWatchParams; result: FsWatchResponse };
"fs/unwatch": { params: FsUnwatchParams; result: FsUnwatchResponse };
"turn/start": { params: TurnStartParams; result: TurnStartResponse };
"turn/steer": { params: TurnSteerParams; result: TurnSteerResponse };
"turn/interrupt": { params: TurnInterruptParams; result: TurnInterruptResponse };
"review/start": { params: ReviewStartParams; result: ReviewStartResponse };
"model/list": { params: ModelListParams; result: ModelListResponse };
"mcpServerStatus/list": { params: ListMcpServerStatusParams; result: ListMcpServerStatusResponse };
"command/exec": { params: CommandExecParams; result: CommandExecResponse };
"command/exec/write": { params: CommandExecWriteParams; result: CommandExecWriteResponse };
"command/exec/terminate": { params: CommandExecTerminateParams; result: CommandExecTerminateResponse };
"command/exec/resize": { params: CommandExecResizeParams; result: CommandExecResizeResponse };
"config/read": { params: ConfigReadParams; result: ConfigReadResponse };
"config/value/write": { params: ConfigValueWriteParams; result: ConfigWriteResponse };
"config/batchWrite": { params: ConfigBatchWriteParams; result: ConfigWriteResponse };
};
export type ClientNotificationMap = {
"initialized": { params: undefined };
};
export type ServerRequestMap = {
"item/commandExecution/requestApproval": { params: CommandExecutionRequestApprovalParams; result: CommandExecutionRequestApprovalResponse };
"item/fileChange/requestApproval": { params: FileChangeRequestApprovalParams; result: FileChangeRequestApprovalResponse };
"item/tool/requestUserInput": { params: ToolRequestUserInputParams; result: ToolRequestUserInputResponse };
"item/permissions/requestApproval": { params: PermissionsRequestApprovalParams; result: PermissionsRequestApprovalResponse };
"item/tool/call": { params: DynamicToolCallParams; result: DynamicToolCallResponse };
};
export type ServerNotificationMap = {
"error": { params: ErrorNotification };
"thread/started": { params: ThreadStartedNotification };
"thread/status/changed": { params: ThreadStatusChangedNotification };
"thread/archived": { params: ThreadArchivedNotification };
"thread/deleted": { params: ThreadDeletedNotification };
"thread/unarchived": { params: ThreadUnarchivedNotification };
"thread/closed": { params: ThreadClosedNotification };
"thread/reverted": { params: ThreadRevertedNotification };
"skills/changed": { params: SkillsChangedNotification };
"thread/name/updated": { params: ThreadNameUpdatedNotification };
"thread/goal/updated": { params: ThreadGoalUpdatedNotification };
"thread/goal/cleared": { params: ThreadGoalClearedNotification };
"thread/tokenUsage/updated": { params: ThreadTokenUsageUpdatedNotification };
"turn/started": { params: TurnStartedNotification };
"turn/completed": { params: TurnCompletedNotification };
"turn/diff/updated": { params: TurnDiffUpdatedNotification };
"turn/plan/updated": { params: TurnPlanUpdatedNotification };
"item/started": { params: ItemStartedNotification };
"item/completed": { params: ItemCompletedNotification };
"item/agentMessage/delta": { params: AgentMessageDeltaNotification };
"item/plan/delta": { params: PlanDeltaNotification };
"command/exec/outputDelta": { params: CommandExecOutputDeltaNotification };
"item/commandExecution/outputDelta": { params: CommandExecutionOutputDeltaNotification };
"item/commandExecution/terminalInteraction": { params: TerminalInteractionNotification };
"item/fileChange/outputDelta": { params: FileChangeOutputDeltaNotification };
"item/fileChange/patchUpdated": { params: FileChangePatchUpdatedNotification };
"serverRequest/resolved": { params: ServerRequestResolvedNotification };
"item/mcpToolCall/progress": { params: McpToolCallProgressNotification };
"fs/changed": { params: FsChangedNotification };
"item/reasoning/summaryTextDelta": { params: ReasoningSummaryTextDeltaNotification };
"item/reasoning/summaryPartAdded": { params: ReasoningSummaryPartAddedNotification };
"item/reasoning/textDelta": { params: ReasoningTextDeltaNotification };
"thread/compacted": { params: ContextCompactedNotification };
};
@@ -0,0 +1,33 @@
#[path = "../codegen/mod.rs"]
mod codegen;
use std::path::PathBuf;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let arguments: Vec<String> = std::env::args().skip(1).collect();
if !matches!(arguments.as_slice(), [mode] if mode == "--write" || mode == "--check") {
return Err("use --write or --check against the contract manifest outputs".into());
}
let write = arguments[0] == "--write";
let package = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let manifest = std::fs::read_to_string(package.join(codegen::MANIFEST_PATH))?;
let pinned = std::fs::read_to_string(package.join(codegen::PINNED_SCHEMA_PATH))?;
let outputs = codegen::generate(&manifest, &pinned)?;
let mut drift = Vec::new();
for (path, text) in outputs.files {
let target = package.join(path);
if write {
if let Some(parent) = target.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(&target, text)?;
} else if std::fs::read_to_string(&target).ok().as_deref() != Some(text.as_str()) {
drift.push(path);
}
}
if drift.is_empty() {
Ok(())
} else {
Err(format!("generated contract drift: {}", drift.join(", ")).into())
}
}
File diff suppressed because it is too large. Load diff
+3
View File
@@ -1,7 +1,10 @@
pub mod lifecycle;
#[rustfmt::skip]
pub mod methods;
pub mod rpc;
pub mod runtime;
#[rustfmt::skip]
pub mod v2;
#[cfg(feature = "schema")]
pub mod schema;
File diff suppressed because it is too large. Load diff
+2 -18
View File
@@ -1,6 +1,8 @@
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value;
pub use crate::v2::{ThreadExecutor, ThreadPlacement as ThreadEnvironment};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(untagged)]
pub enum RequestId {
@@ -149,24 +151,6 @@ pub struct ThreadListItem {
pub environment: Option<ThreadEnvironment>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase")]
pub struct ThreadEnvironment {
pub id: String,
pub provider_kind: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase")]
pub struct ThreadExecutor {
pub kind: String,
pub agent_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub agent_name: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(tag = "type", rename_all = "camelCase")]
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,633 @@
#[path = "../src/codegen/mod.rs"]
mod codegen;
use codewhale_app_server_protocol::methods::{
self, ClientNotificationMethod, ClientRequestMethod, Disposition, ServerNotificationMethod,
ServerRequestMethod, Tier, error_code,
};
use codewhale_app_server_protocol::rpc::RpcCall;
use codewhale_app_server_protocol::v2;
use serde_json::{Map, Value, json};
use std::collections::{BTreeMap, BTreeSet};
const MANIFEST: &str = include_str!("../contract/codex-v2.json");
const PINNED: &str = include_str!("pinned/codex/codex_app_server_protocol.schemas.json");
const COMMON: &str = include_str!("pinned/codex/common.rs");
fn manifest() -> Value {
serde_json::from_str(MANIFEST).unwrap()
}
fn pinned() -> Value {
serde_json::from_str(PINNED).unwrap()
}
#[test]
fn generated_outputs_have_not_drifted_from_the_manifest_and_pinned_export() {
let package = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
let read = |path: &str| std::fs::read_to_string(package.join(path)).unwrap();
assert_eq!(read(codegen::MANIFEST_PATH), MANIFEST);
assert_eq!(read(codegen::PINNED_SCHEMA_PATH), PINNED);
let outputs = codegen::generate(MANIFEST, PINNED).unwrap();
let committed: BTreeMap<&str, &str> = BTreeMap::from([
("src/methods.rs", include_str!("../src/methods.rs")),
("src/v2.rs", include_str!("../src/v2.rs")),
("METHODS.md", include_str!("../METHODS.md")),
(
"schema/json/codex-v2.schema.json",
include_str!("../schema/json/codex-v2.schema.json"),
),
(
"schema/typescript/codex-v2.d.ts",
include_str!("../schema/typescript/codex-v2.d.ts"),
),
]);
assert_eq!(outputs.files.len(), committed.len());
for (path, text) in outputs.files {
assert!(
committed[path] == text,
"{path} drifted; run cargo run -p codewhale-app-server-protocol --bin codewhale-codex-contract -- --write"
);
}
}
const KINDS: [(&str, &str, &str); 4] = [
(
"clientRequests",
include_str!("pinned/codex/ClientRequest.ts"),
"ClientRequest",
),
(
"clientNotifications",
include_str!("pinned/codex/ClientNotification.ts"),
"ClientNotification",
),
(
"serverRequests",
include_str!("pinned/codex/ServerRequest.ts"),
"ServerRequest",
),
(
"serverNotifications",
include_str!("pinned/codex/ServerNotification.ts"),
"ServerNotification",
),
];
fn pinned_ts_methods(source: &str) -> Vec<&str> {
source
.split("\"method\": \"")
.skip(1)
.map(|rest| rest.split('"').next().unwrap())
.collect()
}
fn typed(kind: &str) -> Vec<(&'static str, Tier)> {
match kind {
"clientRequests" => ClientRequestMethod::ALL
.iter()
.map(|method| (method.method(), method.tier()))
.collect(),
"clientNotifications" => ClientNotificationMethod::ALL
.iter()
.map(|method| (method.method(), method.tier()))
.collect(),
"serverRequests" => ServerRequestMethod::ALL
.iter()
.map(|method| (method.method(), method.tier()))
.collect(),
_ => ServerNotificationMethod::ALL
.iter()
.map(|method| (method.method(), method.tier()))
.collect(),
}
}
fn disposition(kind: &str, method: &str) -> Option<Disposition> {
match kind {
"clientRequests" => methods::client_request_disposition(method),
"clientNotifications" => methods::client_notification_disposition(method),
"serverRequests" => methods::server_request_disposition(method),
_ => methods::server_notification_disposition(method),
}
}
fn upstream_response(method: &str) -> String {
let marker = format!("=> \"{method}\" {{");
let start = COMMON
.find(&marker)
.unwrap_or_else(|| panic!("pinned common.rs has no {method}"));
let rest = &COMMON[start..];
let end = rest.find("\n },").unwrap();
let body = &rest[..end];
let response = body
.split("response: ")
.nth(1)
.unwrap_or_else(|| panic!("{method} has no response in common.rs"));
let ty = response.split(',').next().unwrap().trim();
ty.rsplit("::").next().unwrap().to_owned()
}
fn pinned_union_params(pinned: &Value, union: &str, method: &str) -> Option<String> {
pinned["definitions"][union]["oneOf"]
.as_array()
.unwrap()
.iter()
.find(|variant| variant["properties"]["method"]["enum"][0] == method)
.unwrap_or_else(|| panic!("pinned {union} has no {method}"))["properties"]["params"]["$ref"]
.as_str()
.map(|reference| reference.rsplit('/').next().unwrap().to_owned())
}
#[test]
fn manifest_inventory_matches_the_pinned_method_sets_and_typed_enums() {
let manifest = manifest();
let pinned = pinned();
let mut totals: BTreeMap<String, usize> = BTreeMap::new();
for (kind, ts, union) in KINDS {
let pinned_methods = pinned_ts_methods(ts);
let pinned_set: BTreeSet<&str> = pinned_methods.iter().copied().collect();
assert_eq!(pinned_methods.len(), pinned_set.len(), "{kind}: duplicate");
let rows = manifest["methods"][kind].as_array().unwrap();
let mut seen = BTreeSet::new();
for row in rows {
let method = row["method"].as_str().unwrap();
let tier = row["tier"].as_str().unwrap();
let status = row["status"].as_str().unwrap();
assert!(seen.insert(method), "{kind}: {method} twice");
assert!(pinned_set.contains(method), "{kind}: {method} not pinned");
assert!(matches!(tier, "A" | "B" | "C" | "undecided"));
assert!(matches!(
status,
"implemented" | "partial" | "missing" | "method-not-found" | "never-sent"
));
assert_eq!(
tier == "C",
matches!(status, "method-not-found" | "never-sent"),
"{kind}: {method} status {status} does not fit tier {tier}"
);
assert_eq!(
row.get("recommended").is_some(),
tier == "undecided",
"{kind}: {method} recommendation belongs to undecided rows only"
);
let expected = match tier {
"A" => Disposition::Selected(Tier::A),
"B" => Disposition::Selected(Tier::B),
"C" => Disposition::C,
_ => Disposition::Undecided,
};
assert_eq!(
disposition(kind, method),
Some(expected),
"{kind}: {method}"
);
*totals.entry(format!("{kind}/{tier}")).or_default() += 1;
if matches!(tier, "A" | "B") {
let params = pinned_union_params(&pinned, union, method);
assert_eq!(
row["params"].as_str().map(str::to_owned),
params,
"{kind}: {method} params disagree with the pinned {union} union"
);
if matches!(kind, "clientRequests" | "serverRequests") {
assert_eq!(
row["response"].as_str().unwrap(),
upstream_response(method),
"{kind}: {method} result disagrees with pinned common.rs"
);
}
}
}
assert_eq!(
seen, pinned_set,
"{kind}: manifest and pinned export differ"
);
let typed_set: BTreeSet<&str> = typed(kind).iter().map(|(method, _)| *method).collect();
let selected: BTreeSet<&str> = rows
.iter()
.filter(|row| matches!(row["tier"].as_str(), Some("A" | "B")))
.map(|row| row["method"].as_str().unwrap())
.collect();
assert_eq!(
typed_set, selected,
"{kind}: typed enum and selected rows differ"
);
}
eprintln!("inventory totals: {totals:?}");
assert_eq!(totals["clientRequests/A"] + totals["clientRequests/B"], 44);
assert_eq!(totals["serverRequests/A"], 5);
assert_eq!(
totals["serverNotifications/A"] + totals["serverNotifications/B"],
33
);
}
#[test]
fn typed_methods_round_trip_their_wire_names() {
for method in ClientRequestMethod::ALL {
assert_eq!(
ClientRequestMethod::from_method(method.method()),
Some(*method)
);
let wire = serde_json::to_value(method).unwrap();
assert_eq!(wire, method.method());
assert_eq!(
serde_json::from_value::<ClientRequestMethod>(wire).unwrap(),
*method
);
}
for method in ServerNotificationMethod::ALL {
assert_eq!(
ServerNotificationMethod::from_method(method.method()),
Some(*method)
);
}
for method in ServerRequestMethod::ALL {
assert_eq!(
ServerRequestMethod::from_method(method.method()),
Some(*method)
);
}
assert!(serde_json::from_value::<ClientRequestMethod>("feedback/upload".into()).is_err());
}
#[derive(Clone, Copy, PartialEq)]
enum Mode {
Full,
Minimal,
}
struct Instances {
definitions: Value,
}
const DEPTH: usize = 5;
impl Instances {
fn new() -> Self {
let mut definitions = pinned()["definitions"].clone();
definitions["codewhale"] = manifest()["extensions"]["definitions"].clone();
Instances { definitions }
}
fn resolve(&self, reference: &str) -> &Value {
let mut node = &self.definitions;
for part in reference.trim_start_matches("#/definitions/").split('/') {
node = &node[part];
}
assert!(!node.is_null(), "unresolved {reference}");
node
}
fn lookup(&self, name: &str) -> Option<(String, &Value)> {
for namespace in ["v2/", "", "codewhale/"] {
let reference = format!("#/definitions/{namespace}{name}");
let mut node = &self.definitions;
for part in reference.trim_start_matches("#/definitions/").split('/') {
node = &node[part];
}
if !node.is_null() && !(namespace.is_empty() && name == "v2") {
return Some((reference, node));
}
}
None
}
fn validator(&self, reference: &str) -> jsonschema::Validator {
let schema = json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": self.definitions,
"allOf": [{"$ref": reference}],
});
jsonschema::validator_for(&schema).unwrap()
}
fn members<'a>(&self, schema: &'a Value) -> Option<&'a Vec<Value>> {
schema["oneOf"].as_array().or(schema["anyOf"].as_array())
}
fn value(&self, schema: &Value, mode: Mode, depth: usize) -> Value {
if schema == &Value::Bool(true) {
return json!({"any": [1, "x"]});
}
if let Some(reference) = schema["$ref"].as_str() {
return self.value(self.resolve(reference), mode, depth + 1);
}
if let Some(all) = schema["allOf"].as_array() {
return self.value(&all[0], mode, depth);
}
if let Some(members) = self.members(schema) {
let member = members
.iter()
.find(|member| member["type"] != "null")
.unwrap();
return self.member_value(schema, member, mode, depth);
}
if let Some(values) = schema["enum"].as_array() {
return values[0].clone();
}
let ty = match &schema["type"] {
Value::String(ty) => ty.clone(),
Value::Array(list) => list
.iter()
.find(|ty| *ty != "null")
.and_then(Value::as_str)
.unwrap()
.to_owned(),
_ => return json!({"any": [1, "x"]}),
};
match ty.as_str() {
"string" => json!("s"),
"integer" => json!(1),
"boolean" => json!(true),
"array" => {
if mode == Mode::Full && depth < DEPTH {
json!([self.value(&schema["items"], mode, depth + 1)])
} else {
json!([])
}
}
"object" => self.object(schema, mode, depth),
other => panic!("unsupported type {other}"),
}
}
fn object(&self, schema: &Value, mode: Mode, depth: usize) -> Value {
let mut out = Map::new();
let required: BTreeSet<&str> = schema["required"]
.as_array()
.map(|names| names.iter().filter_map(Value::as_str).collect())
.unwrap_or_default();
let full = mode == Mode::Full && depth < DEPTH;
if let Some(properties) = schema["properties"].as_object() {
for (name, property) in properties {
if required.contains(name.as_str()) || full {
out.insert(name.clone(), self.value(property, mode, depth + 1));
} else if let Some(default) =
property.get("default").filter(|value| !value.is_null())
{
out.insert(name.clone(), default.clone());
}
}
if full && schema["additionalProperties"] == true {
out.insert("x-additional".into(), json!(1));
}
} else if full {
match &schema["additionalProperties"] {
Value::Object(_) => {
out.insert(
"k".into(),
self.value(&schema["additionalProperties"], mode, depth + 1),
);
}
Value::Bool(true) => {
out.insert("k".into(), json!(1));
}
_ => {}
}
}
Value::Object(out)
}
fn member_value(&self, schema: &Value, member: &Value, mode: Mode, depth: usize) -> Value {
let mut value = self.value(member, mode, depth);
if schema.get("properties").is_some()
&& let (Value::Object(base), Value::Object(extra)) =
(self.object(schema, mode, depth), &mut value)
{
for (key, field) in base {
extra.entry(key).or_insert(field);
}
}
value
}
fn cases(&self, schema: &Value) -> Vec<(String, Value)> {
let mut cases = vec![
("full".to_owned(), self.value(schema, Mode::Full, 0)),
("minimal".to_owned(), self.value(schema, Mode::Minimal, 0)),
];
if let Some(members) = self.members(schema) {
for (index, member) in members.iter().enumerate() {
if member["type"] == "null" {
continue;
}
if let Some(values) = member["enum"].as_array() {
for value in values {
cases.push((format!("member {index} {value}"), value.clone()));
}
} else {
cases.push((
format!("member {index} full"),
self.member_value(schema, member, Mode::Full, 0),
));
cases.push((
format!("member {index} minimal"),
self.member_value(schema, member, Mode::Minimal, 0),
));
}
}
} else if let Some(values) = schema["enum"].as_array() {
for value in values {
cases.push((format!("enum {value}"), value.clone()));
}
}
cases
}
}
#[test]
fn every_selected_shape_round_trips_against_the_pinned_schema() {
let instances = Instances::new();
let mut checked = 0usize;
let mut typed = 0usize;
let mut inline = Vec::new();
for name in v2::TYPE_NAMES {
let Some((reference, schema)) = instances.lookup(name) else {
inline.push(*name);
continue;
};
typed += 1;
let validator = instances.validator(&reference);
for (case, value) in instances.cases(schema) {
assert!(
validator.is_valid(&value),
"{name} {case}: generated instance is not valid upstream: {value}"
);
let again = v2::round_trip(name, value.clone())
.unwrap()
.unwrap_or_else(|error| {
panic!("{name} {case}: typed decode failed: {error}: {value}")
});
assert!(
validator.is_valid(&again),
"{name} {case}: typed output is not valid upstream: {again}"
);
assert_eq!(
again, value,
"{name} {case}: typed round trip changed the value"
);
checked += 1;
}
}
eprintln!(
"typed definitions with a pinned schema: {typed}; inline helper types: {}; round-tripped instances: {checked}",
inline.len()
);
assert!(typed > 250 && checked > typed * 2);
}
#[test]
fn typed_envelopes_match_the_pinned_unions_without_jsonrpc() {
let instances = Instances::new();
let client_request = instances.validator("#/definitions/ClientRequest");
let server_request = instances.validator("#/definitions/ServerRequest");
let server_notification = instances.validator("#/definitions/ServerNotification");
let client_notification = instances.validator("#/definitions/ClientNotification");
let mut counts = BTreeMap::new();
for method in ClientRequestMethod::ALL {
let (_, params_schema) = instances.lookup(method.params_type().unwrap()).unwrap();
let (_, result_schema) = instances.lookup(method.response_type()).unwrap();
for mode in [Mode::Full, Mode::Minimal] {
let params = instances.value(params_schema, mode, 0);
let envelope = json!({"id": 7, "method": method.method(), "params": params});
assert!(client_request.is_valid(&envelope), "{method}: {envelope}");
let call: RpcCall = serde_json::from_value(envelope).unwrap();
let typed: v2::ClientRequestParams =
serde_json::from_value(json!({"method": call.method, "params": call.params}))
.unwrap_or_else(|error| panic!("{method}: {error}"));
assert_eq!(typed.method(), *method);
let encoded = serde_json::to_value(&typed).unwrap();
assert!(encoded.get("jsonrpc").is_none());
assert_eq!(encoded["params"], params);
let result = instances.value(result_schema, mode, 0);
let response = v2::ClientResponse::decode(*method, result.clone()).unwrap();
assert_eq!(response.method(), *method);
assert_eq!(serde_json::to_value(&response).unwrap(), result);
}
*counts.entry("client requests").or_insert(0) += 1;
}
for method in ServerRequestMethod::ALL {
let (_, params_schema) = instances.lookup(method.params_type().unwrap()).unwrap();
let (_, result_schema) = instances.lookup(method.response_type()).unwrap();
for mode in [Mode::Full, Mode::Minimal] {
let params = instances.value(params_schema, mode, 0);
let typed: v2::ServerRequestParams =
serde_json::from_value(json!({"method": method.method(), "params": params}))
.unwrap();
let mut encoded = serde_json::to_value(&typed).unwrap();
encoded["id"] = json!("server-1");
assert!(server_request.is_valid(&encoded), "{method}: {encoded}");
let result = instances.value(result_schema, mode, 0);
let answer = v2::ServerRequestResponse::decode(*method, result.clone()).unwrap();
assert_eq!(answer.method(), *method);
assert_eq!(serde_json::to_value(&answer).unwrap(), result);
}
*counts.entry("server requests").or_insert(0) += 1;
}
for method in ServerNotificationMethod::ALL {
let (_, params_schema) = instances.lookup(method.params_type().unwrap()).unwrap();
for mode in [Mode::Full, Mode::Minimal] {
let params = instances.value(params_schema, mode, 0);
let typed: v2::ServerNotification =
serde_json::from_value(json!({"method": method.method(), "params": params}))
.unwrap();
assert_eq!(typed.method(), *method);
let encoded = serde_json::to_value(&typed).unwrap();
assert!(encoded.get("jsonrpc").is_none() && encoded.get("id").is_none());
assert!(
server_notification.is_valid(&encoded),
"{method}: {encoded}"
);
}
*counts.entry("server notifications").or_insert(0) += 1;
}
let initialized = serde_json::to_value(v2::ClientNotification::Initialized).unwrap();
assert_eq!(initialized, json!({"method": "initialized"}));
assert!(client_notification.is_valid(&initialized));
eprintln!("typed envelopes checked: {counts:?}");
assert_eq!(counts["client requests"], 44);
assert_eq!(counts["server requests"], 5);
assert_eq!(counts["server notifications"], 33);
}
#[test]
fn tier_c_and_undecided_methods_are_method_not_found_and_never_typed() {
let manifest = manifest();
let mut refused = 0;
for (kind, _, _) in KINDS {
for row in manifest["methods"][kind].as_array().unwrap() {
if matches!(row["tier"].as_str(), Some("A" | "B")) {
continue;
}
let method = row["method"].as_str().unwrap();
assert!(!disposition(kind, method).unwrap().selected());
let envelope = json!({"method": method, "params": {}});
match kind {
"clientRequests" => {
assert_eq!(
methods::unselected_request_error(method),
Some(error_code::METHOD_NOT_FOUND)
);
assert!(ClientRequestMethod::from_method(method).is_none());
assert!(serde_json::from_value::<v2::ClientRequestParams>(envelope).is_err());
}
"serverRequests" => {
assert!(ServerRequestMethod::from_method(method).is_none());
assert!(serde_json::from_value::<v2::ServerRequestParams>(envelope).is_err());
}
"serverNotifications" => {
assert!(ServerNotificationMethod::from_method(method).is_none());
assert!(serde_json::from_value::<v2::ServerNotification>(envelope).is_err());
}
_ => {}
}
refused += 1;
}
}
assert_eq!(error_code::METHOD_NOT_FOUND, -32601);
assert_eq!(methods::unselected_request_error("thread/start"), None);
assert_eq!(
methods::unselected_request_error("not/in/the/export"),
Some(error_code::METHOD_NOT_FOUND)
);
eprintln!("unselected methods refused: {refused}");
assert!(refused > 100);
}
#[test]
fn codewhale_extensions_stay_optional_and_valid_upstream() {
let instances = Instances::new();
let thread = instances.validator("#/definitions/v2/Thread");
let (_, schema) = instances.lookup("Thread").unwrap();
let mut value = instances.value(schema, Mode::Minimal, 0);
let plain: v2::Thread = serde_json::from_value(value.clone()).unwrap();
assert!(plain.executor.is_none() && plain.environment.is_none());
assert_eq!(serde_json::to_value(&plain).unwrap(), value);
value["executor"] = json!({"kind": "acp", "agentId": "claude", "agentName": "Claude"});
value["environment"] = json!({"id": "env-1", "providerKind": "remote"});
assert!(thread.is_valid(&value));
let placed: v2::Thread = serde_json::from_value(value.clone()).unwrap();
assert_eq!(placed.executor.as_ref().unwrap().agent_id, "claude");
assert_eq!(serde_json::to_value(&placed).unwrap(), value);
let refusal = instances.validator("#/definitions/codewhale/CapabilityRefusalData");
let data = serde_json::to_value(v2::CapabilityRefusalData {
codewhale_refusal: v2::CapabilityRefusal {
code: v2::CapabilityRefusalCode::EnvironmentNotSupported,
method: Some("turn/start".into()),
thread_id: Some("thread-1".into()),
},
})
.unwrap();
assert!(refusal.is_valid(&data));
assert_eq!(
data["codewhaleRefusal"]["code"],
"environment_not_supported"
);
assert_eq!(error_code::CAPABILITY_UNSUPPORTED, -32020);
assert!(
serde_json::from_value::<v2::CapabilityRefusal>(json!({"code": "no_acp", "token": "x"}))
.is_err()
);
}
-219
View File
@@ -1,219 +0,0 @@
use codewhale_app_server_protocol::methods::{
ClientNotificationMethod, ClientRequestMethod, ServerNotificationMethod, ServerRequestMethod,
Tier,
};
use std::collections::{BTreeMap, BTreeSet};
const INVENTORY: &str = include_str!("../METHODS.md");
struct Kind {
section: &'static str,
pinned: &'static str,
typed: Vec<(&'static str, Tier)>,
}
fn kinds() -> [Kind; 4] {
fn typed<T: Copy>(
all: &[T],
method: fn(T) -> &'static str,
tier: fn(T) -> Tier,
) -> Vec<(&'static str, Tier)> {
all.iter()
.map(|value| (method(*value), tier(*value)))
.collect()
}
[
Kind {
section: "Client requests",
pinned: include_str!("pinned/codex/ClientRequest.ts"),
typed: typed(
ClientRequestMethod::ALL,
ClientRequestMethod::method,
ClientRequestMethod::tier,
),
},
Kind {
section: "Client notifications",
pinned: include_str!("pinned/codex/ClientNotification.ts"),
typed: typed(
ClientNotificationMethod::ALL,
ClientNotificationMethod::method,
ClientNotificationMethod::tier,
),
},
Kind {
section: "Server requests",
pinned: include_str!("pinned/codex/ServerRequest.ts"),
typed: typed(
ServerRequestMethod::ALL,
ServerRequestMethod::method,
ServerRequestMethod::tier,
),
},
Kind {
section: "Server notifications",
pinned: include_str!("pinned/codex/ServerNotification.ts"),
typed: typed(
ServerNotificationMethod::ALL,
ServerNotificationMethod::method,
ServerNotificationMethod::tier,
),
},
]
}
fn pinned_methods(source: &str) -> Vec<&str> {
source
.split("\"method\": \"")
.skip(1)
.map(|rest| rest.split('"').next().expect("method literal"))
.collect()
}
fn inventory_rows(section: &str) -> Vec<(String, String, String)> {
let heading = format!("## {section}\n");
let start = INVENTORY
.find(&heading)
.unwrap_or_else(|| panic!("inventory has no section {section}"))
+ heading.len();
INVENTORY[start..]
.lines()
.take_while(|line| !line.starts_with("## "))
.filter(|line| line.starts_with("| `"))
.map(|line| {
let cells = line
.trim_matches('|')
.split('|')
.map(str::trim)
.collect::<Vec<_>>();
assert_eq!(cells.len(), 5, "inventory row shape: {line}");
(
cells[0].trim_matches('`').to_owned(),
cells[1].to_owned(),
cells[2].to_owned(),
)
})
.collect()
}
#[test]
fn typed_methods_inventory_and_pinned_export_agree() {
let mut totals = BTreeMap::new();
for kind in kinds() {
let pinned = pinned_methods(kind.pinned);
let pinned_set = pinned.iter().copied().collect::<BTreeSet<_>>();
assert_eq!(
pinned.len(),
pinned_set.len(),
"{}: duplicate pinned method",
kind.section
);
let rows = inventory_rows(kind.section);
let mut inventory = BTreeMap::new();
for (method, tier, status) in &rows {
assert!(
matches!(tier.as_str(), "A" | "B" | "C" | "undecided"),
"{}: {method} has tier {tier}",
kind.section
);
assert!(
matches!(
status.as_str(),
"implemented" | "partial" | "missing" | "method-not-found" | "never-sent"
),
"{}: {method} has status {status}",
kind.section
);
assert_eq!(
tier == "C",
matches!(status.as_str(), "method-not-found" | "never-sent"),
"{}: {method} status {status} does not fit tier {tier}",
kind.section
);
assert!(
inventory.insert(method.as_str(), tier.as_str()).is_none(),
"{}: {method} listed twice",
kind.section
);
*totals.entry(tier.clone()).or_insert(0usize) += 1;
}
let inventory_set = inventory.keys().copied().collect::<BTreeSet<_>>();
assert_eq!(
pinned_set.difference(&inventory_set).collect::<Vec<_>>(),
Vec::<&&str>::new(),
"{}: pinned export names methods the inventory lacks",
kind.section
);
assert_eq!(
inventory_set.difference(&pinned_set).collect::<Vec<_>>(),
Vec::<&&str>::new(),
"{}: inventory names methods the pinned export lacks",
kind.section
);
let mut typed = BTreeSet::new();
for (method, tier) in &kind.typed {
assert!(
typed.insert(*method),
"{}: {method} typed twice",
kind.section
);
assert!(
pinned_set.contains(method),
"{}: typed {method} is not in the pinned export",
kind.section
);
let expected = match tier {
Tier::A => "A",
Tier::B => "B",
};
assert_eq!(
inventory.get(method).copied(),
Some(expected),
"{}: typed {method} disagrees with the inventory tier",
kind.section
);
}
for (method, tier) in &inventory {
if matches!(*tier, "A" | "B") {
assert!(
typed.contains(method),
"{}: Tier {tier} {method} has no typed variant",
kind.section
);
}
}
}
assert!(totals.values().sum::<usize>() > 0);
}
#[test]
fn typed_methods_round_trip_their_wire_names() {
for method in ClientRequestMethod::ALL {
assert_eq!(
ClientRequestMethod::from_method(method.method()),
Some(*method)
);
let wire = serde_json::to_value(method).expect("serialize method");
assert_eq!(wire, method.method());
assert_eq!(
serde_json::from_value::<ClientRequestMethod>(wire).expect("deserialize method"),
*method
);
}
for method in ServerNotificationMethod::ALL {
assert_eq!(
ServerNotificationMethod::from_method(method.method()),
Some(*method)
);
}
for method in ServerRequestMethod::ALL {
assert_eq!(
ServerRequestMethod::from_method(method.method()),
Some(*method)
);
}
assert_eq!(ClientRequestMethod::from_method("account/read"), None);
assert!(serde_json::from_value::<ClientRequestMethod>("feedback/upload".into()).is_err());
}
File diff suppressed because it is too large. Load diff