mirror of
https://github.com/openai/codex.git
synced 2026-03-07 15:13:23 +00:00
Compare commits
9 Commits
dev/friel/
...
dev/sayan/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
464d4da7f6 | ||
|
|
81e641c6ea | ||
|
|
7fe4a43713 | ||
|
|
584919cd54 | ||
|
|
459766b4ad | ||
|
|
d8d86fb24f | ||
|
|
974d553983 | ||
|
|
e0b157279f | ||
|
|
3c9cb670ca |
23
.github/workflows/rust-release.yml
vendored
23
.github/workflows/rust-release.yml
vendored
@@ -643,29 +643,6 @@ jobs:
|
||||
exit "${publish_status}"
|
||||
done
|
||||
|
||||
winget:
|
||||
name: winget
|
||||
needs: release
|
||||
# Only publish stable/mainline releases to WinGet; pre-releases include a
|
||||
# '-' in the semver string (e.g., 1.2.3-alpha.1).
|
||||
if: ${{ !contains(needs.release.outputs.version, '-') }}
|
||||
# This job only invokes a GitHub Action to open/update the winget-pkgs PR;
|
||||
# it does not execute Windows-only tooling, so Linux is sufficient.
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Publish to WinGet
|
||||
uses: vedantmgoyal9/winget-releaser@19e706d4c9121098010096f9c495a70a7518b30f
|
||||
with:
|
||||
identifier: OpenAI.Codex
|
||||
version: ${{ needs.release.outputs.version }}
|
||||
release-tag: ${{ needs.release.outputs.tag }}
|
||||
fork-user: openai-oss-forks
|
||||
installers-regex: '^codex-(?:x86_64|aarch64)-pc-windows-msvc\.exe\.zip$'
|
||||
token: ${{ secrets.WINGET_PUBLISH_PAT }}
|
||||
|
||||
update-branch:
|
||||
name: Update latest-alpha-cli branch
|
||||
permissions:
|
||||
|
||||
10
codex-rs/Cargo.lock
generated
10
codex-rs/Cargo.lock
generated
@@ -1469,7 +1469,6 @@ dependencies = [
|
||||
"codex-utils-cargo-bin",
|
||||
"inventory",
|
||||
"pretty_assertions",
|
||||
"rmcp",
|
||||
"schemars 0.8.22",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -1549,7 +1548,6 @@ dependencies = [
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"url",
|
||||
"which",
|
||||
"wiremock",
|
||||
"zip",
|
||||
]
|
||||
@@ -1647,8 +1645,6 @@ dependencies = [
|
||||
"tokio",
|
||||
"toml 0.9.11+spec-1.1.0",
|
||||
"tracing",
|
||||
"tracing-appender",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1989,6 +1985,7 @@ dependencies = [
|
||||
"sentry",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2090,7 +2087,6 @@ dependencies = [
|
||||
"codex-app-server-protocol",
|
||||
"codex-core",
|
||||
"core_test_support",
|
||||
"pretty_assertions",
|
||||
"rand 0.9.2",
|
||||
"reqwest",
|
||||
"serde",
|
||||
@@ -2099,7 +2095,6 @@ dependencies = [
|
||||
"tempfile",
|
||||
"tiny_http",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"url",
|
||||
"urlencoding",
|
||||
"webbrowser",
|
||||
@@ -2217,7 +2212,6 @@ dependencies = [
|
||||
name = "codex-package-manager"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"fd-lock",
|
||||
"flate2",
|
||||
"pretty_assertions",
|
||||
"reqwest",
|
||||
@@ -2306,9 +2300,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serial_test",
|
||||
"sha2",
|
||||
"sse-stream",
|
||||
"tempfile",
|
||||
"thiserror 2.0.18",
|
||||
"tiny_http",
|
||||
"tokio",
|
||||
"tracing",
|
||||
|
||||
@@ -178,7 +178,6 @@ dirs = "6"
|
||||
dotenvy = "0.15.7"
|
||||
dunce = "1.0.4"
|
||||
encoding_rs = "0.8.35"
|
||||
fd-lock = "4.0.4"
|
||||
env-flags = "0.1.1"
|
||||
env_logger = "0.11.9"
|
||||
eventsource-stream = "0.2.3"
|
||||
|
||||
@@ -51,7 +51,6 @@ You can enable notifications by configuring a script that is run whenever the ag
|
||||
### `codex exec` to run Codex programmatically/non-interactively
|
||||
|
||||
To run Codex non-interactively, run `codex exec PROMPT` (you can also pass the prompt via `stdin`) and Codex will work on your task until it decides that it is done and exits. Output is printed to the terminal directly. You can set the `RUST_LOG` environment variable to see more about what's going on.
|
||||
Use `codex exec --fork <SESSION_ID> PROMPT` to fork an existing session without launching the interactive picker/UI.
|
||||
Use `codex exec --ephemeral ...` to run without persisting session rollout files to disk.
|
||||
|
||||
### Experimenting with the Codex Sandbox
|
||||
|
||||
@@ -24,12 +24,6 @@ serde_with = { workspace = true }
|
||||
shlex = { workspace = true }
|
||||
strum_macros = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
rmcp = { workspace = true, default-features = false, features = [
|
||||
"base64",
|
||||
"macros",
|
||||
"schemars",
|
||||
"server",
|
||||
] }
|
||||
ts-rs = { workspace = true }
|
||||
inventory = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
@@ -953,34 +953,25 @@
|
||||
},
|
||||
"PluginInstallParams": {
|
||||
"properties": {
|
||||
"marketplacePath": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"cwd": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"marketplaceName": {
|
||||
"type": "string"
|
||||
},
|
||||
"pluginName": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"marketplacePath",
|
||||
"marketplaceName",
|
||||
"pluginName"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginListParams": {
|
||||
"properties": {
|
||||
"cwds": {
|
||||
"description": "Optional working directories used to discover repo marketplaces. When omitted, only home-scoped marketplaces are considered.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ProductSurface": {
|
||||
"enum": [
|
||||
"chatgpt",
|
||||
@@ -3273,30 +3264,6 @@
|
||||
"title": "Skills/listRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"plugin/list"
|
||||
],
|
||||
"title": "Plugin/listRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/PluginListParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Plugin/listRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
|
||||
@@ -31,24 +31,38 @@
|
||||
"AdditionalMacOsPermissions": {
|
||||
"properties": {
|
||||
"accessibility": {
|
||||
"type": "boolean"
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"automations": {
|
||||
"$ref": "#/definitions/MacOsAutomationPermission"
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MacOsAutomationValue"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"calendar": {
|
||||
"type": "boolean"
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"preferences": {
|
||||
"$ref": "#/definitions/MacOsPreferencesPermission"
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MacOsPreferencesValue"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accessibility",
|
||||
"automations",
|
||||
"calendar",
|
||||
"preferences"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AdditionalNetworkPermissions": {
|
||||
@@ -286,40 +300,28 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"MacOsAutomationPermission": {
|
||||
"oneOf": [
|
||||
"MacOsAutomationValue": {
|
||||
"anyOf": [
|
||||
{
|
||||
"enum": [
|
||||
"none",
|
||||
"all"
|
||||
],
|
||||
"type": "string"
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"bundle_ids": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"required": [
|
||||
"bundle_ids"
|
||||
],
|
||||
"title": "BundleIdsMacOsAutomationPermission",
|
||||
"type": "object"
|
||||
"type": "array"
|
||||
}
|
||||
]
|
||||
},
|
||||
"MacOsPreferencesPermission": {
|
||||
"enum": [
|
||||
"none",
|
||||
"read_only",
|
||||
"read_write"
|
||||
],
|
||||
"type": "string"
|
||||
"MacOsPreferencesValue": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"NetworkApprovalContext": {
|
||||
"properties": {
|
||||
|
||||
@@ -29,14 +29,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"AgentSpawnMode": {
|
||||
"enum": [
|
||||
"spawn",
|
||||
"fork",
|
||||
"watchdog"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"AgentStatus": {
|
||||
"description": "Agent lifecycle status, derived from emitted events.",
|
||||
"oneOf": [
|
||||
@@ -552,58 +544,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"ElicitationRequest": {
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"_meta": true,
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"mode": {
|
||||
"enum": [
|
||||
"form"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"requested_schema": true
|
||||
},
|
||||
"required": [
|
||||
"message",
|
||||
"mode",
|
||||
"requested_schema"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"_meta": true,
|
||||
"elicitation_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"mode": {
|
||||
"enum": [
|
||||
"url"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"elicitation_id",
|
||||
"message",
|
||||
"mode",
|
||||
"url"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"EventMsg": {
|
||||
"description": "Response event from the agent NOTE: Make sure none of these values have optional types, as it will mess up the extension code-gen.",
|
||||
"oneOf": [
|
||||
@@ -1481,12 +1421,6 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"saved_path": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -2029,19 +1963,12 @@
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"request": {
|
||||
"$ref": "#/definitions/ElicitationRequest"
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"server_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"turn_id": {
|
||||
"description": "Turn ID that this elicitation belongs to, when known.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"elicitation_request"
|
||||
@@ -2052,7 +1979,7 @@
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"request",
|
||||
"message",
|
||||
"server_name",
|
||||
"type"
|
||||
],
|
||||
@@ -2997,15 +2924,6 @@
|
||||
],
|
||||
"description": "Thread ID of the sender."
|
||||
},
|
||||
"spawn_mode": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AgentSpawnMode"
|
||||
}
|
||||
],
|
||||
"default": "spawn",
|
||||
"description": "Spawn mode used for this agent."
|
||||
},
|
||||
"status": {
|
||||
"allOf": [
|
||||
{
|
||||
@@ -3788,70 +3706,66 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"MacOsAutomationPermission": {
|
||||
"oneOf": [
|
||||
"MacOsAutomationValue": {
|
||||
"anyOf": [
|
||||
{
|
||||
"enum": [
|
||||
"none",
|
||||
"all"
|
||||
],
|
||||
"type": "string"
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"bundle_ids": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"required": [
|
||||
"bundle_ids"
|
||||
],
|
||||
"title": "BundleIdsMacOsAutomationPermission",
|
||||
"type": "object"
|
||||
"type": "array"
|
||||
}
|
||||
]
|
||||
},
|
||||
"MacOsPreferencesPermission": {
|
||||
"enum": [
|
||||
"none",
|
||||
"read_only",
|
||||
"read_write"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"MacOsSeatbeltProfileExtensions": {
|
||||
"MacOsPermissions": {
|
||||
"properties": {
|
||||
"macos_accessibility": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
"accessibility": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"macos_automation": {
|
||||
"allOf": [
|
||||
"automations": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MacOsAutomationPermission"
|
||||
}
|
||||
],
|
||||
"default": "none"
|
||||
},
|
||||
"macos_calendar": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"macos_preferences": {
|
||||
"allOf": [
|
||||
"$ref": "#/definitions/MacOsAutomationValue"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/MacOsPreferencesPermission"
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": "read_only"
|
||||
]
|
||||
},
|
||||
"calendar": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"preferences": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MacOsPreferencesValue"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"MacOsPreferencesValue": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"McpAuthStatus": {
|
||||
"enum": [
|
||||
"unsupported",
|
||||
@@ -4195,7 +4109,7 @@
|
||||
"macos": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MacOsSeatbeltProfileExtensions"
|
||||
"$ref": "#/definitions/MacOsPermissions"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
@@ -5641,6 +5555,9 @@
|
||||
},
|
||||
"SessionNetworkProxyRuntime": {
|
||||
"properties": {
|
||||
"admin_addr": {
|
||||
"type": "string"
|
||||
},
|
||||
"http_addr": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -5649,6 +5566,7 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"admin_addr",
|
||||
"http_addr",
|
||||
"socks_addr"
|
||||
],
|
||||
@@ -6151,12 +6069,6 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"saved_path": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -6298,7 +6210,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "Explicit structured mention selected by the user.\n\n`path` identifies the exact mention target, for example `app://<connector-id>` or `plugin://<plugin-name>@<marketplace-name>`.",
|
||||
"description": "Explicit mention selected by the user (name + app://connector id).",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
@@ -7302,12 +7214,6 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"saved_path": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -7850,19 +7756,12 @@
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"request": {
|
||||
"$ref": "#/definitions/ElicitationRequest"
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"server_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"turn_id": {
|
||||
"description": "Turn ID that this elicitation belongs to, when known.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"elicitation_request"
|
||||
@@ -7873,7 +7772,7 @@
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"request",
|
||||
"message",
|
||||
"server_name",
|
||||
"type"
|
||||
],
|
||||
@@ -8818,15 +8717,6 @@
|
||||
],
|
||||
"description": "Thread ID of the sender."
|
||||
},
|
||||
"spawn_mode": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AgentSpawnMode"
|
||||
}
|
||||
],
|
||||
"default": "spawn",
|
||||
"description": "Spawn mode used for this agent."
|
||||
},
|
||||
"status": {
|
||||
"allOf": [
|
||||
{
|
||||
|
||||
@@ -1,609 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"McpElicitationArrayType": {
|
||||
"enum": [
|
||||
"array"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"McpElicitationBooleanSchema": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"default": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"description": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"title": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/definitions/McpElicitationBooleanType"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpElicitationBooleanType": {
|
||||
"enum": [
|
||||
"boolean"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"McpElicitationConstOption": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"const": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"const",
|
||||
"title"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpElicitationEnumSchema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/McpElicitationSingleSelectEnumSchema"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/McpElicitationMultiSelectEnumSchema"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/McpElicitationLegacyTitledEnumSchema"
|
||||
}
|
||||
]
|
||||
},
|
||||
"McpElicitationLegacyTitledEnumSchema": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"default": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"description": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"enum": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"enumNames": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"title": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/definitions/McpElicitationStringType"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"enum",
|
||||
"type"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpElicitationMultiSelectEnumSchema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/McpElicitationUntitledMultiSelectEnumSchema"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/McpElicitationTitledMultiSelectEnumSchema"
|
||||
}
|
||||
]
|
||||
},
|
||||
"McpElicitationNumberSchema": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"default": {
|
||||
"format": "double",
|
||||
"type": [
|
||||
"number",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"description": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"maximum": {
|
||||
"format": "double",
|
||||
"type": [
|
||||
"number",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"minimum": {
|
||||
"format": "double",
|
||||
"type": [
|
||||
"number",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"title": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/definitions/McpElicitationNumberType"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpElicitationNumberType": {
|
||||
"enum": [
|
||||
"number",
|
||||
"integer"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"McpElicitationObjectType": {
|
||||
"enum": [
|
||||
"object"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"McpElicitationPrimitiveSchema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/McpElicitationEnumSchema"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/McpElicitationStringSchema"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/McpElicitationNumberSchema"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/McpElicitationBooleanSchema"
|
||||
}
|
||||
]
|
||||
},
|
||||
"McpElicitationSchema": {
|
||||
"additionalProperties": false,
|
||||
"description": "Typed form schema for MCP `elicitation/create` requests.\n\nThis matches the `requestedSchema` shape from the MCP 2025-11-25 `ElicitRequestFormParams` schema.",
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/McpElicitationPrimitiveSchema"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"required": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/definitions/McpElicitationObjectType"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"properties",
|
||||
"type"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpElicitationSingleSelectEnumSchema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/McpElicitationUntitledSingleSelectEnumSchema"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/McpElicitationTitledSingleSelectEnumSchema"
|
||||
}
|
||||
]
|
||||
},
|
||||
"McpElicitationStringFormat": {
|
||||
"enum": [
|
||||
"email",
|
||||
"uri",
|
||||
"date",
|
||||
"date-time"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"McpElicitationStringSchema": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"default": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"description": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"format": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/McpElicitationStringFormat"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"maxLength": {
|
||||
"format": "uint32",
|
||||
"minimum": 0.0,
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"minLength": {
|
||||
"format": "uint32",
|
||||
"minimum": 0.0,
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"title": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/definitions/McpElicitationStringType"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpElicitationStringType": {
|
||||
"enum": [
|
||||
"string"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"McpElicitationTitledEnumItems": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"anyOf": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/McpElicitationConstOption"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"anyOf"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpElicitationTitledMultiSelectEnumSchema": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"default": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"description": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"items": {
|
||||
"$ref": "#/definitions/McpElicitationTitledEnumItems"
|
||||
},
|
||||
"maxItems": {
|
||||
"format": "uint64",
|
||||
"minimum": 0.0,
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"minItems": {
|
||||
"format": "uint64",
|
||||
"minimum": 0.0,
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"title": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/definitions/McpElicitationArrayType"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"items",
|
||||
"type"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpElicitationTitledSingleSelectEnumSchema": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"default": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"description": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"oneOf": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/McpElicitationConstOption"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"title": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/definitions/McpElicitationStringType"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"oneOf",
|
||||
"type"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpElicitationUntitledEnumItems": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"enum": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/definitions/McpElicitationStringType"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"enum",
|
||||
"type"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpElicitationUntitledMultiSelectEnumSchema": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"default": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"description": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"items": {
|
||||
"$ref": "#/definitions/McpElicitationUntitledEnumItems"
|
||||
},
|
||||
"maxItems": {
|
||||
"format": "uint64",
|
||||
"minimum": 0.0,
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"minItems": {
|
||||
"format": "uint64",
|
||||
"minimum": 0.0,
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"title": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/definitions/McpElicitationArrayType"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"items",
|
||||
"type"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpElicitationUntitledSingleSelectEnumSchema": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"default": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"description": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"enum": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"title": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/definitions/McpElicitationStringType"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"enum",
|
||||
"type"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"_meta": true,
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"mode": {
|
||||
"enum": [
|
||||
"form"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"requestedSchema": {
|
||||
"$ref": "#/definitions/McpElicitationSchema"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"message",
|
||||
"mode",
|
||||
"requestedSchema"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"_meta": true,
|
||||
"elicitationId": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"mode": {
|
||||
"enum": [
|
||||
"url"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"elicitationId",
|
||||
"message",
|
||||
"mode",
|
||||
"url"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"serverName": {
|
||||
"type": "string"
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
},
|
||||
"turnId": {
|
||||
"description": "Active Codex turn when this elicitation was observed, if app-server could correlate one.\n\nThis is nullable because MCP models elicitation as a standalone server-to-client request identified by the MCP server request id. It may be triggered during a turn, but turn context is app-server correlation rather than part of the protocol identity of the elicitation itself.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"serverName",
|
||||
"threadId"
|
||||
],
|
||||
"title": "McpServerElicitationRequestParams",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"McpServerElicitationAction": {
|
||||
"enum": [
|
||||
"accept",
|
||||
"decline",
|
||||
"cancel"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"_meta": {
|
||||
"description": "Optional client metadata for form-mode action handling."
|
||||
},
|
||||
"action": {
|
||||
"$ref": "#/definitions/McpServerElicitationAction"
|
||||
},
|
||||
"content": {
|
||||
"description": "Structured user input for accepted elicitations, mirroring RMCP `CreateElicitationResult`.\n\nThis is nullable because decline/cancel responses have no content."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"action"
|
||||
],
|
||||
"title": "McpServerElicitationRequestResponse",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -201,13 +201,6 @@
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"pluginDisplayNames": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
||||
@@ -31,24 +31,38 @@
|
||||
"AdditionalMacOsPermissions": {
|
||||
"properties": {
|
||||
"accessibility": {
|
||||
"type": "boolean"
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"automations": {
|
||||
"$ref": "#/definitions/MacOsAutomationPermission"
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MacOsAutomationValue"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"calendar": {
|
||||
"type": "boolean"
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"preferences": {
|
||||
"$ref": "#/definitions/MacOsPreferencesPermission"
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MacOsPreferencesValue"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accessibility",
|
||||
"automations",
|
||||
"calendar",
|
||||
"preferences"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AdditionalNetworkPermissions": {
|
||||
@@ -615,645 +629,28 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"MacOsAutomationPermission": {
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"none",
|
||||
"all"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"bundle_ids": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"bundle_ids"
|
||||
],
|
||||
"title": "BundleIdsMacOsAutomationPermission",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"MacOsPreferencesPermission": {
|
||||
"enum": [
|
||||
"none",
|
||||
"read_only",
|
||||
"read_write"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"McpElicitationArrayType": {
|
||||
"enum": [
|
||||
"array"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"McpElicitationBooleanSchema": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"default": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"description": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"title": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/definitions/McpElicitationBooleanType"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpElicitationBooleanType": {
|
||||
"enum": [
|
||||
"boolean"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"McpElicitationConstOption": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"const": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"const",
|
||||
"title"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpElicitationEnumSchema": {
|
||||
"MacOsAutomationValue": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/McpElicitationSingleSelectEnumSchema"
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/McpElicitationMultiSelectEnumSchema"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/McpElicitationLegacyTitledEnumSchema"
|
||||
}
|
||||
]
|
||||
},
|
||||
"McpElicitationLegacyTitledEnumSchema": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"default": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"description": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"enum": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"enumNames": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"title": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/definitions/McpElicitationStringType"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"enum",
|
||||
"type"
|
||||
],
|
||||
"type": "object"
|
||||
]
|
||||
},
|
||||
"McpElicitationMultiSelectEnumSchema": {
|
||||
"MacOsPreferencesValue": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/McpElicitationUntitledMultiSelectEnumSchema"
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/McpElicitationTitledMultiSelectEnumSchema"
|
||||
}
|
||||
]
|
||||
},
|
||||
"McpElicitationNumberSchema": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"default": {
|
||||
"format": "double",
|
||||
"type": [
|
||||
"number",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"description": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"maximum": {
|
||||
"format": "double",
|
||||
"type": [
|
||||
"number",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"minimum": {
|
||||
"format": "double",
|
||||
"type": [
|
||||
"number",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"title": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/definitions/McpElicitationNumberType"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpElicitationNumberType": {
|
||||
"enum": [
|
||||
"number",
|
||||
"integer"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"McpElicitationObjectType": {
|
||||
"enum": [
|
||||
"object"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"McpElicitationPrimitiveSchema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/McpElicitationEnumSchema"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/McpElicitationStringSchema"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/McpElicitationNumberSchema"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/McpElicitationBooleanSchema"
|
||||
}
|
||||
]
|
||||
},
|
||||
"McpElicitationSchema": {
|
||||
"additionalProperties": false,
|
||||
"description": "Typed form schema for MCP `elicitation/create` requests.\n\nThis matches the `requestedSchema` shape from the MCP 2025-11-25 `ElicitRequestFormParams` schema.",
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/McpElicitationPrimitiveSchema"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"required": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/definitions/McpElicitationObjectType"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"properties",
|
||||
"type"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpElicitationSingleSelectEnumSchema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/McpElicitationUntitledSingleSelectEnumSchema"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/McpElicitationTitledSingleSelectEnumSchema"
|
||||
}
|
||||
]
|
||||
},
|
||||
"McpElicitationStringFormat": {
|
||||
"enum": [
|
||||
"email",
|
||||
"uri",
|
||||
"date",
|
||||
"date-time"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"McpElicitationStringSchema": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"default": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"description": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"format": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/McpElicitationStringFormat"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"maxLength": {
|
||||
"format": "uint32",
|
||||
"minimum": 0.0,
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"minLength": {
|
||||
"format": "uint32",
|
||||
"minimum": 0.0,
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"title": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/definitions/McpElicitationStringType"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpElicitationStringType": {
|
||||
"enum": [
|
||||
"string"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"McpElicitationTitledEnumItems": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"anyOf": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/McpElicitationConstOption"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"anyOf"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpElicitationTitledMultiSelectEnumSchema": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"default": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"description": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"items": {
|
||||
"$ref": "#/definitions/McpElicitationTitledEnumItems"
|
||||
},
|
||||
"maxItems": {
|
||||
"format": "uint64",
|
||||
"minimum": 0.0,
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"minItems": {
|
||||
"format": "uint64",
|
||||
"minimum": 0.0,
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"title": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/definitions/McpElicitationArrayType"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"items",
|
||||
"type"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpElicitationTitledSingleSelectEnumSchema": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"default": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"description": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"oneOf": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/McpElicitationConstOption"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"title": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/definitions/McpElicitationStringType"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"oneOf",
|
||||
"type"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpElicitationUntitledEnumItems": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"enum": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/definitions/McpElicitationStringType"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"enum",
|
||||
"type"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpElicitationUntitledMultiSelectEnumSchema": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"default": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"description": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"items": {
|
||||
"$ref": "#/definitions/McpElicitationUntitledEnumItems"
|
||||
},
|
||||
"maxItems": {
|
||||
"format": "uint64",
|
||||
"minimum": 0.0,
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"minItems": {
|
||||
"format": "uint64",
|
||||
"minimum": 0.0,
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"title": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/definitions/McpElicitationArrayType"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"items",
|
||||
"type"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpElicitationUntitledSingleSelectEnumSchema": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"default": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"description": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"enum": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"title": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/definitions/McpElicitationStringType"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"enum",
|
||||
"type"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpServerElicitationRequestParams": {
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"_meta": true,
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"mode": {
|
||||
"enum": [
|
||||
"form"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"requestedSchema": {
|
||||
"$ref": "#/definitions/McpElicitationSchema"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"message",
|
||||
"mode",
|
||||
"requestedSchema"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"_meta": true,
|
||||
"elicitationId": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"mode": {
|
||||
"enum": [
|
||||
"url"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"elicitationId",
|
||||
"message",
|
||||
"mode",
|
||||
"url"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"serverName": {
|
||||
"type": "string"
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
},
|
||||
"turnId": {
|
||||
"description": "Active Codex turn when this elicitation was observed, if app-server could correlate one.\n\nThis is nullable because MCP models elicitation as a standalone server-to-client request identified by the MCP server request id. It may be triggered during a turn, but turn context is app-server correlation rather than part of the protocol identity of the elicitation itself.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"serverName",
|
||||
"threadId"
|
||||
],
|
||||
"type": "object"
|
||||
]
|
||||
},
|
||||
"NetworkApprovalContext": {
|
||||
"properties": {
|
||||
@@ -1584,31 +981,6 @@
|
||||
"title": "Item/tool/requestUserInputRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "Request input for an MCP server elicitation.",
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"mcpServer/elicitation/request"
|
||||
],
|
||||
"title": "McpServer/elicitation/requestRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/McpServerElicitationRequestParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "McpServer/elicitation/requestRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "Execute a dynamic tool call on the client.",
|
||||
"properties": {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -163,14 +163,6 @@
|
||||
"title": "AgentMessageDeltaNotification",
|
||||
"type": "object"
|
||||
},
|
||||
"AgentSpawnMode": {
|
||||
"enum": [
|
||||
"spawn",
|
||||
"fork",
|
||||
"watchdog"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"AgentStatus": {
|
||||
"description": "Agent lifecycle status, derived from emitted events.",
|
||||
"oneOf": [
|
||||
@@ -412,13 +404,6 @@
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"pluginDisplayNames": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -568,34 +553,6 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AppSummary": {
|
||||
"description": "EXPERIMENTAL - app metadata summary for plugin-install responses.",
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"installUrl": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AppToolApproval": {
|
||||
"enum": [
|
||||
"auto",
|
||||
@@ -1247,30 +1204,6 @@
|
||||
"title": "Skills/listRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"plugin/list"
|
||||
],
|
||||
"title": "Plugin/listRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/PluginListParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Plugin/listRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -3353,58 +3286,6 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ElicitationRequest": {
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"_meta": true,
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"mode": {
|
||||
"enum": [
|
||||
"form"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"requested_schema": true
|
||||
},
|
||||
"required": [
|
||||
"message",
|
||||
"mode",
|
||||
"requested_schema"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"_meta": true,
|
||||
"elicitation_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"mode": {
|
||||
"enum": [
|
||||
"url"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"elicitation_id",
|
||||
"message",
|
||||
"mode",
|
||||
"url"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ErrorNotification": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
@@ -4308,12 +4189,6 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"saved_path": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -4856,19 +4731,12 @@
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"request": {
|
||||
"$ref": "#/definitions/ElicitationRequest"
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"server_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"turn_id": {
|
||||
"description": "Turn ID that this elicitation belongs to, when known.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"elicitation_request"
|
||||
@@ -4879,7 +4747,7 @@
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"request",
|
||||
"message",
|
||||
"server_name",
|
||||
"type"
|
||||
],
|
||||
@@ -5824,15 +5692,6 @@
|
||||
],
|
||||
"description": "Thread ID of the sender."
|
||||
},
|
||||
"spawn_mode": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AgentSpawnMode"
|
||||
}
|
||||
],
|
||||
"default": "spawn",
|
||||
"description": "Spawn mode used for this agent."
|
||||
},
|
||||
"status": {
|
||||
"allOf": [
|
||||
{
|
||||
@@ -7434,70 +7293,66 @@
|
||||
"title": "LogoutAccountResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"MacOsAutomationPermission": {
|
||||
"oneOf": [
|
||||
"MacOsAutomationValue": {
|
||||
"anyOf": [
|
||||
{
|
||||
"enum": [
|
||||
"none",
|
||||
"all"
|
||||
],
|
||||
"type": "string"
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"bundle_ids": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"required": [
|
||||
"bundle_ids"
|
||||
],
|
||||
"title": "BundleIdsMacOsAutomationPermission",
|
||||
"type": "object"
|
||||
"type": "array"
|
||||
}
|
||||
]
|
||||
},
|
||||
"MacOsPreferencesPermission": {
|
||||
"enum": [
|
||||
"none",
|
||||
"read_only",
|
||||
"read_write"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"MacOsSeatbeltProfileExtensions": {
|
||||
"MacOsPermissions": {
|
||||
"properties": {
|
||||
"macos_accessibility": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
"accessibility": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"macos_automation": {
|
||||
"allOf": [
|
||||
"automations": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MacOsAutomationPermission"
|
||||
}
|
||||
],
|
||||
"default": "none"
|
||||
},
|
||||
"macos_calendar": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"macos_preferences": {
|
||||
"allOf": [
|
||||
"$ref": "#/definitions/MacOsAutomationValue"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/MacOsPreferencesPermission"
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": "read_only"
|
||||
]
|
||||
},
|
||||
"calendar": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"preferences": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MacOsPreferencesValue"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"MacOsPreferencesValue": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"McpAuthStatus": {
|
||||
"enum": [
|
||||
"unsupported",
|
||||
@@ -8118,6 +7973,12 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"dangerouslyAllowNonLoopbackAdmin": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"dangerouslyAllowNonLoopbackProxy": {
|
||||
"type": [
|
||||
"boolean",
|
||||
@@ -8368,7 +8229,7 @@
|
||||
"macos": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MacOsSeatbeltProfileExtensions"
|
||||
"$ref": "#/definitions/MacOsPermissions"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
@@ -8455,15 +8316,21 @@
|
||||
"PluginInstallParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"marketplacePath": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"cwd": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"marketplaceName": {
|
||||
"type": "string"
|
||||
},
|
||||
"pluginName": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"marketplacePath",
|
||||
"marketplaceName",
|
||||
"pluginName"
|
||||
],
|
||||
"title": "PluginInstallParams",
|
||||
@@ -8471,118 +8338,9 @@
|
||||
},
|
||||
"PluginInstallResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"appsNeedingAuth": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/AppSummary"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"appsNeedingAuth"
|
||||
],
|
||||
"title": "PluginInstallResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"PluginListParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"cwds": {
|
||||
"description": "Optional working directories used to discover repo marketplaces. When omitted, only home-scoped marketplaces are considered.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"title": "PluginListParams",
|
||||
"type": "object"
|
||||
},
|
||||
"PluginListResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"marketplaces": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/PluginMarketplaceEntry"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"marketplaces"
|
||||
],
|
||||
"title": "PluginListResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"PluginMarketplaceEntry": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"plugins": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/PluginSummary"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"path",
|
||||
"plugins"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginSource": {
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"local"
|
||||
],
|
||||
"title": "LocalPluginSourceType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path",
|
||||
"type"
|
||||
],
|
||||
"title": "LocalPluginSource",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"PluginSummary": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"source": {
|
||||
"$ref": "#/definitions/PluginSource"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"enabled",
|
||||
"name",
|
||||
"source"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ProductSurface": {
|
||||
"enum": [
|
||||
"chatgpt",
|
||||
@@ -11159,6 +10917,9 @@
|
||||
},
|
||||
"SessionNetworkProxyRuntime": {
|
||||
"properties": {
|
||||
"admin_addr": {
|
||||
"type": "string"
|
||||
},
|
||||
"http_addr": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -11167,6 +10928,7 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"admin_addr",
|
||||
"http_addr",
|
||||
"socks_addr"
|
||||
],
|
||||
@@ -14085,12 +13847,6 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"saved_path": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -119,13 +119,6 @@
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"pluginDisplayNames": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
||||
@@ -119,13 +119,6 @@
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"pluginDisplayNames": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
||||
@@ -132,6 +132,12 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"dangerouslyAllowNonLoopbackAdmin": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"dangerouslyAllowNonLoopbackProxy": {
|
||||
"type": [
|
||||
"boolean",
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"AbsolutePathBuf": {
|
||||
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"marketplacePath": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"cwd": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"marketplaceName": {
|
||||
"type": "string"
|
||||
},
|
||||
"pluginName": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"marketplacePath",
|
||||
"marketplaceName",
|
||||
"pluginName"
|
||||
],
|
||||
"title": "PluginInstallParams",
|
||||
|
||||
@@ -1,46 +1,5 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"AppSummary": {
|
||||
"description": "EXPERIMENTAL - app metadata summary for plugin-install responses.",
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"installUrl": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"appsNeedingAuth": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/AppSummary"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"appsNeedingAuth"
|
||||
],
|
||||
"title": "PluginInstallResponse",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"AbsolutePathBuf": {
|
||||
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"cwds": {
|
||||
"description": "Optional working directories used to discover repo marketplaces. When omitted, only home-scoped marketplaces are considered.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"title": "PluginListParams",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"PluginMarketplaceEntry": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"plugins": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/PluginSummary"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"path",
|
||||
"plugins"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginSource": {
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"local"
|
||||
],
|
||||
"title": "LocalPluginSourceType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path",
|
||||
"type"
|
||||
],
|
||||
"title": "LocalPluginSource",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"PluginSummary": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"source": {
|
||||
"$ref": "#/definitions/PluginSource"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"enabled",
|
||||
"name",
|
||||
"source"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"marketplaces": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/PluginMarketplaceEntry"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"marketplaces"
|
||||
],
|
||||
"title": "PluginListResponse",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -23,7 +23,6 @@ import type { LoginAccountParams } from "./v2/LoginAccountParams";
|
||||
import type { McpServerOauthLoginParams } from "./v2/McpServerOauthLoginParams";
|
||||
import type { ModelListParams } from "./v2/ModelListParams";
|
||||
import type { PluginInstallParams } from "./v2/PluginInstallParams";
|
||||
import type { PluginListParams } from "./v2/PluginListParams";
|
||||
import type { ReviewStartParams } from "./v2/ReviewStartParams";
|
||||
import type { SkillsConfigWriteParams } from "./v2/SkillsConfigWriteParams";
|
||||
import type { SkillsListParams } from "./v2/SkillsListParams";
|
||||
@@ -50,4 +49,4 @@ import type { WindowsSandboxSetupStartParams } from "./v2/WindowsSandboxSetupSta
|
||||
/**
|
||||
* Request from the client to the server.
|
||||
*/
|
||||
export type ClientRequest ={ "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/unsubscribe", id: RequestId, params: ThreadUnsubscribeParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/metadata/update", id: RequestId, params: ThreadMetadataUpdateParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/compact/start", id: RequestId, params: ThreadCompactStartParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "plugin/list", id: RequestId, params: PluginListParams, } | { "method": "skills/remote/list", id: RequestId, params: SkillsRemoteReadParams, } | { "method": "skills/remote/export", id: RequestId, params: SkillsRemoteWriteParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "plugin/install", id: RequestId, params: PluginInstallParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/steer", id: RequestId, params: TurnSteerParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "experimentalFeature/list", id: RequestId, params: ExperimentalFeatureListParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "windowsSandbox/setupStart", id: RequestId, params: WindowsSandboxSetupStartParams, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "externalAgentConfig/detect", id: RequestId, params: ExternalAgentConfigDetectParams, } | { "method": "externalAgentConfig/import", id: RequestId, params: ExternalAgentConfigImportParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, };
|
||||
export type ClientRequest ={ "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/unsubscribe", id: RequestId, params: ThreadUnsubscribeParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/metadata/update", id: RequestId, params: ThreadMetadataUpdateParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/compact/start", id: RequestId, params: ThreadCompactStartParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "skills/remote/list", id: RequestId, params: SkillsRemoteReadParams, } | { "method": "skills/remote/export", id: RequestId, params: SkillsRemoteWriteParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "plugin/install", id: RequestId, params: PluginInstallParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/steer", id: RequestId, params: TurnSteerParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "experimentalFeature/list", id: RequestId, params: ExperimentalFeatureListParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "windowsSandbox/setupStart", id: RequestId, params: WindowsSandboxSetupStartParams, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "externalAgentConfig/detect", id: RequestId, params: ExternalAgentConfigDetectParams, } | { "method": "externalAgentConfig/import", id: RequestId, params: ExternalAgentConfigImportParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, };
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { AgentSpawnMode } from "./AgentSpawnMode";
|
||||
import type { AgentStatus } from "./AgentStatus";
|
||||
import type { ThreadId } from "./ThreadId";
|
||||
|
||||
@@ -31,10 +30,6 @@ new_agent_role?: string | null,
|
||||
* beginning.
|
||||
*/
|
||||
prompt: string,
|
||||
/**
|
||||
* Spawn mode used for this agent.
|
||||
*/
|
||||
spawn_mode: AgentSpawnMode,
|
||||
/**
|
||||
* Last known status of the new agent reported to the sender agent.
|
||||
*/
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { JsonValue } from "./serde_json/JsonValue";
|
||||
|
||||
export type ElicitationRequest = { "mode": "form", _meta?: JsonValue, message: string, requested_schema: JsonValue, } | { "mode": "url", _meta?: JsonValue, message: string, url: string, elicitation_id: string, };
|
||||
@@ -1,10 +1,5 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { ElicitationRequest } from "./ElicitationRequest";
|
||||
|
||||
export type ElicitationRequestEvent = {
|
||||
/**
|
||||
* Turn ID that this elicitation belongs to, when known.
|
||||
*/
|
||||
turn_id?: string, server_name: string, id: string | number, request: ElicitationRequest, };
|
||||
export type ElicitationRequestEvent = { server_name: string, id: string | number, message: string, };
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type ImageGenerationEndEvent = { call_id: string, status: string, revised_prompt?: string, result: string, saved_path?: string, };
|
||||
export type ImageGenerationEndEvent = { call_id: string, status: string, revised_prompt?: string, result: string, };
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type ImageGenerationItem = { id: string, status: string, revised_prompt?: string, result: string, saved_path?: string, };
|
||||
export type ImageGenerationItem = { id: string, status: string, revised_prompt?: string, result: string, };
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type MacOsAutomationPermission = "none" | "all" | { "bundle_ids": Array<string> };
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type AgentSpawnMode = "spawn" | "fork" | "watchdog";
|
||||
export type MacOsAutomationValue = boolean | Array<string>;
|
||||
@@ -0,0 +1,7 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { MacOsAutomationValue } from "./MacOsAutomationValue";
|
||||
import type { MacOsPreferencesValue } from "./MacOsPreferencesValue";
|
||||
|
||||
export type MacOsPermissions = { preferences: MacOsPreferencesValue | null, automations: MacOsAutomationValue | null, accessibility: boolean | null, calendar: boolean | null, };
|
||||
@@ -1,5 +0,0 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type MacOsPreferencesPermission = "none" | "read_only" | "read_write";
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type McpElicitationStringType = "string";
|
||||
export type MacOsPreferencesValue = boolean | string;
|
||||
@@ -1,7 +0,0 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { MacOsAutomationPermission } from "./MacOsAutomationPermission";
|
||||
import type { MacOsPreferencesPermission } from "./MacOsPreferencesPermission";
|
||||
|
||||
export type MacOsSeatbeltProfileExtensions = { macos_preferences: MacOsPreferencesPermission, macos_automation: MacOsAutomationPermission, macos_accessibility: boolean, macos_calendar: boolean, };
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { FileSystemPermissions } from "./FileSystemPermissions";
|
||||
import type { MacOsSeatbeltProfileExtensions } from "./MacOsSeatbeltProfileExtensions";
|
||||
import type { MacOsPermissions } from "./MacOsPermissions";
|
||||
import type { NetworkPermissions } from "./NetworkPermissions";
|
||||
|
||||
export type PermissionProfile = { network: NetworkPermissions | null, file_system: FileSystemPermissions | null, macos: MacOsSeatbeltProfileExtensions | null, };
|
||||
export type PermissionProfile = { network: NetworkPermissions | null, file_system: FileSystemPermissions | null, macos: MacOsPermissions | null, };
|
||||
|
||||
@@ -8,10 +8,9 @@ import type { ChatgptAuthTokensRefreshParams } from "./v2/ChatgptAuthTokensRefre
|
||||
import type { CommandExecutionRequestApprovalParams } from "./v2/CommandExecutionRequestApprovalParams";
|
||||
import type { DynamicToolCallParams } from "./v2/DynamicToolCallParams";
|
||||
import type { FileChangeRequestApprovalParams } from "./v2/FileChangeRequestApprovalParams";
|
||||
import type { McpServerElicitationRequestParams } from "./v2/McpServerElicitationRequestParams";
|
||||
import type { ToolRequestUserInputParams } from "./v2/ToolRequestUserInputParams";
|
||||
|
||||
/**
|
||||
* Request initiated from the server and sent to the client.
|
||||
*/
|
||||
export type ServerRequest = { "method": "item/commandExecution/requestApproval", id: RequestId, params: CommandExecutionRequestApprovalParams, } | { "method": "item/fileChange/requestApproval", id: RequestId, params: FileChangeRequestApprovalParams, } | { "method": "item/tool/requestUserInput", id: RequestId, params: ToolRequestUserInputParams, } | { "method": "mcpServer/elicitation/request", id: RequestId, params: McpServerElicitationRequestParams, } | { "method": "item/tool/call", id: RequestId, params: DynamicToolCallParams, } | { "method": "account/chatgptAuthTokens/refresh", id: RequestId, params: ChatgptAuthTokensRefreshParams, } | { "method": "applyPatchApproval", id: RequestId, params: ApplyPatchApprovalParams, } | { "method": "execCommandApproval", id: RequestId, params: ExecCommandApprovalParams, };
|
||||
export type ServerRequest = { "method": "item/commandExecution/requestApproval", id: RequestId, params: CommandExecutionRequestApprovalParams, } | { "method": "item/fileChange/requestApproval", id: RequestId, params: FileChangeRequestApprovalParams, } | { "method": "item/tool/requestUserInput", id: RequestId, params: ToolRequestUserInputParams, } | { "method": "item/tool/call", id: RequestId, params: DynamicToolCallParams, } | { "method": "account/chatgptAuthTokens/refresh", id: RequestId, params: ChatgptAuthTokensRefreshParams, } | { "method": "applyPatchApproval", id: RequestId, params: ApplyPatchApprovalParams, } | { "method": "execCommandApproval", id: RequestId, params: ExecCommandApprovalParams, };
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type SessionNetworkProxyRuntime = { http_addr: string, socks_addr: string, };
|
||||
export type SessionNetworkProxyRuntime = { http_addr: string, socks_addr: string, admin_addr: string, };
|
||||
|
||||
@@ -11,7 +11,6 @@ export type { AgentReasoningEvent } from "./AgentReasoningEvent";
|
||||
export type { AgentReasoningRawContentDeltaEvent } from "./AgentReasoningRawContentDeltaEvent";
|
||||
export type { AgentReasoningRawContentEvent } from "./AgentReasoningRawContentEvent";
|
||||
export type { AgentReasoningSectionBreakEvent } from "./AgentReasoningSectionBreakEvent";
|
||||
export type { AgentSpawnMode } from "./AgentSpawnMode";
|
||||
export type { AgentStatus } from "./AgentStatus";
|
||||
export type { ApplyPatchApprovalParams } from "./ApplyPatchApprovalParams";
|
||||
export type { ApplyPatchApprovalRequestEvent } from "./ApplyPatchApprovalRequestEvent";
|
||||
@@ -49,7 +48,6 @@ export type { DeprecationNoticeEvent } from "./DeprecationNoticeEvent";
|
||||
export type { DynamicToolCallOutputContentItem } from "./DynamicToolCallOutputContentItem";
|
||||
export type { DynamicToolCallRequest } from "./DynamicToolCallRequest";
|
||||
export type { DynamicToolCallResponseEvent } from "./DynamicToolCallResponseEvent";
|
||||
export type { ElicitationRequest } from "./ElicitationRequest";
|
||||
export type { ElicitationRequestEvent } from "./ElicitationRequestEvent";
|
||||
export type { ErrorEvent } from "./ErrorEvent";
|
||||
export type { EventMsg } from "./EventMsg";
|
||||
@@ -101,9 +99,9 @@ export type { ListSkillsResponseEvent } from "./ListSkillsResponseEvent";
|
||||
export type { LocalShellAction } from "./LocalShellAction";
|
||||
export type { LocalShellExecAction } from "./LocalShellExecAction";
|
||||
export type { LocalShellStatus } from "./LocalShellStatus";
|
||||
export type { MacOsAutomationPermission } from "./MacOsAutomationPermission";
|
||||
export type { MacOsPreferencesPermission } from "./MacOsPreferencesPermission";
|
||||
export type { MacOsSeatbeltProfileExtensions } from "./MacOsSeatbeltProfileExtensions";
|
||||
export type { MacOsAutomationValue } from "./MacOsAutomationValue";
|
||||
export type { MacOsPermissions } from "./MacOsPermissions";
|
||||
export type { MacOsPreferencesValue } from "./MacOsPreferencesValue";
|
||||
export type { McpAuthStatus } from "./McpAuthStatus";
|
||||
export type { McpInvocation } from "./McpInvocation";
|
||||
export type { McpListToolsResponseEvent } from "./McpListToolsResponseEvent";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { MacOsAutomationPermission } from "../MacOsAutomationPermission";
|
||||
import type { MacOsPreferencesPermission } from "../MacOsPreferencesPermission";
|
||||
import type { MacOsAutomationValue } from "../MacOsAutomationValue";
|
||||
import type { MacOsPreferencesValue } from "../MacOsPreferencesValue";
|
||||
|
||||
export type AdditionalMacOsPermissions = { preferences: MacOsPreferencesPermission, automations: MacOsAutomationPermission, accessibility: boolean, calendar: boolean, };
|
||||
export type AdditionalMacOsPermissions = { preferences: MacOsPreferencesValue | null, automations: MacOsAutomationValue | null, accessibility: boolean | null, calendar: boolean | null, };
|
||||
|
||||
@@ -16,4 +16,4 @@ export type AppInfo = { id: string, name: string, description: string | null, lo
|
||||
* enabled = false
|
||||
* ```
|
||||
*/
|
||||
isEnabled: boolean, pluginDisplayNames: Array<string>, };
|
||||
isEnabled: boolean, };
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* EXPERIMENTAL - app metadata summary for plugin-install responses.
|
||||
*/
|
||||
export type AppSummary = { id: string, name: string, description: string | null, installUrl: string | null, };
|
||||
@@ -1,5 +0,0 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type McpElicitationArrayType = "array";
|
||||
@@ -1,6 +0,0 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { McpElicitationBooleanType } from "./McpElicitationBooleanType";
|
||||
|
||||
export type McpElicitationBooleanSchema = { type: McpElicitationBooleanType, title?: string, description?: string, default?: boolean, };
|
||||
@@ -1,5 +0,0 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type McpElicitationBooleanType = "boolean";
|
||||
@@ -1,5 +0,0 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type McpElicitationConstOption = { const: string, title: string, };
|
||||
@@ -1,8 +0,0 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { McpElicitationLegacyTitledEnumSchema } from "./McpElicitationLegacyTitledEnumSchema";
|
||||
import type { McpElicitationMultiSelectEnumSchema } from "./McpElicitationMultiSelectEnumSchema";
|
||||
import type { McpElicitationSingleSelectEnumSchema } from "./McpElicitationSingleSelectEnumSchema";
|
||||
|
||||
export type McpElicitationEnumSchema = McpElicitationSingleSelectEnumSchema | McpElicitationMultiSelectEnumSchema | McpElicitationLegacyTitledEnumSchema;
|
||||
@@ -1,6 +0,0 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { McpElicitationStringType } from "./McpElicitationStringType";
|
||||
|
||||
export type McpElicitationLegacyTitledEnumSchema = { type: McpElicitationStringType, title?: string, description?: string, enum: Array<string>, enumNames?: Array<string>, default?: string, };
|
||||
@@ -1,7 +0,0 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { McpElicitationTitledMultiSelectEnumSchema } from "./McpElicitationTitledMultiSelectEnumSchema";
|
||||
import type { McpElicitationUntitledMultiSelectEnumSchema } from "./McpElicitationUntitledMultiSelectEnumSchema";
|
||||
|
||||
export type McpElicitationMultiSelectEnumSchema = McpElicitationUntitledMultiSelectEnumSchema | McpElicitationTitledMultiSelectEnumSchema;
|
||||
@@ -1,6 +0,0 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { McpElicitationNumberType } from "./McpElicitationNumberType";
|
||||
|
||||
export type McpElicitationNumberSchema = { type: McpElicitationNumberType, title?: string, description?: string, minimum?: number, maximum?: number, default?: number, };
|
||||
@@ -1,5 +0,0 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type McpElicitationNumberType = "number" | "integer";
|
||||
@@ -1,5 +0,0 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type McpElicitationObjectType = "object";
|
||||
@@ -1,9 +0,0 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { McpElicitationBooleanSchema } from "./McpElicitationBooleanSchema";
|
||||
import type { McpElicitationEnumSchema } from "./McpElicitationEnumSchema";
|
||||
import type { McpElicitationNumberSchema } from "./McpElicitationNumberSchema";
|
||||
import type { McpElicitationStringSchema } from "./McpElicitationStringSchema";
|
||||
|
||||
export type McpElicitationPrimitiveSchema = McpElicitationEnumSchema | McpElicitationStringSchema | McpElicitationNumberSchema | McpElicitationBooleanSchema;
|
||||
@@ -1,13 +0,0 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { McpElicitationObjectType } from "./McpElicitationObjectType";
|
||||
import type { McpElicitationPrimitiveSchema } from "./McpElicitationPrimitiveSchema";
|
||||
|
||||
/**
|
||||
* Typed form schema for MCP `elicitation/create` requests.
|
||||
*
|
||||
* This matches the `requestedSchema` shape from the MCP 2025-11-25
|
||||
* `ElicitRequestFormParams` schema.
|
||||
*/
|
||||
export type McpElicitationSchema = { $schema?: string, type: McpElicitationObjectType, properties: { [key in string]?: McpElicitationPrimitiveSchema }, required?: Array<string>, };
|
||||
@@ -1,7 +0,0 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { McpElicitationTitledSingleSelectEnumSchema } from "./McpElicitationTitledSingleSelectEnumSchema";
|
||||
import type { McpElicitationUntitledSingleSelectEnumSchema } from "./McpElicitationUntitledSingleSelectEnumSchema";
|
||||
|
||||
export type McpElicitationSingleSelectEnumSchema = McpElicitationUntitledSingleSelectEnumSchema | McpElicitationTitledSingleSelectEnumSchema;
|
||||
@@ -1,5 +0,0 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type McpElicitationStringFormat = "email" | "uri" | "date" | "date-time";
|
||||
@@ -1,7 +0,0 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { McpElicitationStringFormat } from "./McpElicitationStringFormat";
|
||||
import type { McpElicitationStringType } from "./McpElicitationStringType";
|
||||
|
||||
export type McpElicitationStringSchema = { type: McpElicitationStringType, title?: string, description?: string, minLength?: number, maxLength?: number, format?: McpElicitationStringFormat, default?: string, };
|
||||
@@ -1,6 +0,0 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { McpElicitationConstOption } from "./McpElicitationConstOption";
|
||||
|
||||
export type McpElicitationTitledEnumItems = { anyOf: Array<McpElicitationConstOption>, };
|
||||
@@ -1,7 +0,0 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { McpElicitationArrayType } from "./McpElicitationArrayType";
|
||||
import type { McpElicitationTitledEnumItems } from "./McpElicitationTitledEnumItems";
|
||||
|
||||
export type McpElicitationTitledMultiSelectEnumSchema = { type: McpElicitationArrayType, title?: string, description?: string, minItems?: bigint, maxItems?: bigint, items: McpElicitationTitledEnumItems, default?: Array<string>, };
|
||||
@@ -1,7 +0,0 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { McpElicitationConstOption } from "./McpElicitationConstOption";
|
||||
import type { McpElicitationStringType } from "./McpElicitationStringType";
|
||||
|
||||
export type McpElicitationTitledSingleSelectEnumSchema = { type: McpElicitationStringType, title?: string, description?: string, oneOf: Array<McpElicitationConstOption>, default?: string, };
|
||||
@@ -1,6 +0,0 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { McpElicitationStringType } from "./McpElicitationStringType";
|
||||
|
||||
export type McpElicitationUntitledEnumItems = { type: McpElicitationStringType, enum: Array<string>, };
|
||||
@@ -1,7 +0,0 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { McpElicitationArrayType } from "./McpElicitationArrayType";
|
||||
import type { McpElicitationUntitledEnumItems } from "./McpElicitationUntitledEnumItems";
|
||||
|
||||
export type McpElicitationUntitledMultiSelectEnumSchema = { type: McpElicitationArrayType, title?: string, description?: string, minItems?: bigint, maxItems?: bigint, items: McpElicitationUntitledEnumItems, default?: Array<string>, };
|
||||
@@ -1,6 +0,0 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { McpElicitationStringType } from "./McpElicitationStringType";
|
||||
|
||||
export type McpElicitationUntitledSingleSelectEnumSchema = { type: McpElicitationStringType, title?: string, description?: string, enum: Array<string>, default?: string, };
|
||||
@@ -1,5 +0,0 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type McpServerElicitationAction = "accept" | "decline" | "cancel";
|
||||
@@ -1,16 +0,0 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { JsonValue } from "../serde_json/JsonValue";
|
||||
import type { McpElicitationSchema } from "./McpElicitationSchema";
|
||||
|
||||
export type McpServerElicitationRequestParams = { threadId: string,
|
||||
/**
|
||||
* Active Codex turn when this elicitation was observed, if app-server could correlate one.
|
||||
*
|
||||
* This is nullable because MCP models elicitation as a standalone server-to-client request
|
||||
* identified by the MCP server request id. It may be triggered during a turn, but turn
|
||||
* context is app-server correlation rather than part of the protocol identity of the
|
||||
* elicitation itself.
|
||||
*/
|
||||
turnId: string | null, serverName: string, } & ({ "mode": "form", _meta: JsonValue | null, message: string, requestedSchema: McpElicitationSchema, } | { "mode": "url", _meta: JsonValue | null, message: string, url: string, elicitationId: string, });
|
||||
@@ -1,17 +0,0 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { JsonValue } from "../serde_json/JsonValue";
|
||||
import type { McpServerElicitationAction } from "./McpServerElicitationAction";
|
||||
|
||||
export type McpServerElicitationRequestResponse = { action: McpServerElicitationAction,
|
||||
/**
|
||||
* Structured user input for accepted elicitations, mirroring RMCP `CreateElicitationResult`.
|
||||
*
|
||||
* This is nullable because decline/cancel responses have no content.
|
||||
*/
|
||||
content: JsonValue | null,
|
||||
/**
|
||||
* Optional client metadata for form-mode action handling.
|
||||
*/
|
||||
_meta: JsonValue | null, };
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type NetworkRequirements = { enabled: boolean | null, httpPort: number | null, socksPort: number | null, allowUpstreamProxy: boolean | null, dangerouslyAllowNonLoopbackProxy: boolean | null, dangerouslyAllowAllUnixSockets: boolean | null, allowedDomains: Array<string> | null, deniedDomains: Array<string> | null, allowUnixSockets: Array<string> | null, allowLocalBinding: boolean | null, };
|
||||
export type NetworkRequirements = { enabled: boolean | null, httpPort: number | null, socksPort: number | null, allowUpstreamProxy: boolean | null, dangerouslyAllowNonLoopbackProxy: boolean | null, dangerouslyAllowNonLoopbackAdmin: boolean | null, dangerouslyAllowAllUnixSockets: boolean | null, allowedDomains: Array<string> | null, deniedDomains: Array<string> | null, allowUnixSockets: Array<string> | null, allowLocalBinding: boolean | null, };
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { AbsolutePathBuf } from "../AbsolutePathBuf";
|
||||
|
||||
export type PluginInstallParams = { marketplacePath: AbsolutePathBuf, pluginName: string, };
|
||||
export type PluginInstallParams = { marketplaceName: string, pluginName: string, cwd?: string | null, };
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { AppSummary } from "./AppSummary";
|
||||
|
||||
export type PluginInstallResponse = { appsNeedingAuth: Array<AppSummary>, };
|
||||
export type PluginInstallResponse = Record<string, never>;
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { AbsolutePathBuf } from "../AbsolutePathBuf";
|
||||
|
||||
export type PluginListParams = {
|
||||
/**
|
||||
* Optional working directories used to discover repo marketplaces. When omitted,
|
||||
* only home-scoped marketplaces are considered.
|
||||
*/
|
||||
cwds?: Array<AbsolutePathBuf> | null, };
|
||||
@@ -1,6 +0,0 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { PluginMarketplaceEntry } from "./PluginMarketplaceEntry";
|
||||
|
||||
export type PluginListResponse = { marketplaces: Array<PluginMarketplaceEntry>, };
|
||||
@@ -1,6 +0,0 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { PluginSummary } from "./PluginSummary";
|
||||
|
||||
export type PluginMarketplaceEntry = { name: string, path: string, plugins: Array<PluginSummary>, };
|
||||
@@ -1,5 +0,0 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type PluginSource = { "type": "local", path: string, };
|
||||
@@ -1,6 +0,0 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { PluginSource } from "./PluginSource";
|
||||
|
||||
export type PluginSummary = { name: string, source: PluginSource, enabled: boolean, };
|
||||
@@ -16,7 +16,6 @@ export type { AppListUpdatedNotification } from "./AppListUpdatedNotification";
|
||||
export type { AppMetadata } from "./AppMetadata";
|
||||
export type { AppReview } from "./AppReview";
|
||||
export type { AppScreenshot } from "./AppScreenshot";
|
||||
export type { AppSummary } from "./AppSummary";
|
||||
export type { AppToolApproval } from "./AppToolApproval";
|
||||
export type { AppToolsConfig } from "./AppToolsConfig";
|
||||
export type { AppsConfig } from "./AppsConfig";
|
||||
@@ -98,31 +97,6 @@ export type { LoginAccountParams } from "./LoginAccountParams";
|
||||
export type { LoginAccountResponse } from "./LoginAccountResponse";
|
||||
export type { LogoutAccountResponse } from "./LogoutAccountResponse";
|
||||
export type { McpAuthStatus } from "./McpAuthStatus";
|
||||
export type { McpElicitationArrayType } from "./McpElicitationArrayType";
|
||||
export type { McpElicitationBooleanSchema } from "./McpElicitationBooleanSchema";
|
||||
export type { McpElicitationBooleanType } from "./McpElicitationBooleanType";
|
||||
export type { McpElicitationConstOption } from "./McpElicitationConstOption";
|
||||
export type { McpElicitationEnumSchema } from "./McpElicitationEnumSchema";
|
||||
export type { McpElicitationLegacyTitledEnumSchema } from "./McpElicitationLegacyTitledEnumSchema";
|
||||
export type { McpElicitationMultiSelectEnumSchema } from "./McpElicitationMultiSelectEnumSchema";
|
||||
export type { McpElicitationNumberSchema } from "./McpElicitationNumberSchema";
|
||||
export type { McpElicitationNumberType } from "./McpElicitationNumberType";
|
||||
export type { McpElicitationObjectType } from "./McpElicitationObjectType";
|
||||
export type { McpElicitationPrimitiveSchema } from "./McpElicitationPrimitiveSchema";
|
||||
export type { McpElicitationSchema } from "./McpElicitationSchema";
|
||||
export type { McpElicitationSingleSelectEnumSchema } from "./McpElicitationSingleSelectEnumSchema";
|
||||
export type { McpElicitationStringFormat } from "./McpElicitationStringFormat";
|
||||
export type { McpElicitationStringSchema } from "./McpElicitationStringSchema";
|
||||
export type { McpElicitationStringType } from "./McpElicitationStringType";
|
||||
export type { McpElicitationTitledEnumItems } from "./McpElicitationTitledEnumItems";
|
||||
export type { McpElicitationTitledMultiSelectEnumSchema } from "./McpElicitationTitledMultiSelectEnumSchema";
|
||||
export type { McpElicitationTitledSingleSelectEnumSchema } from "./McpElicitationTitledSingleSelectEnumSchema";
|
||||
export type { McpElicitationUntitledEnumItems } from "./McpElicitationUntitledEnumItems";
|
||||
export type { McpElicitationUntitledMultiSelectEnumSchema } from "./McpElicitationUntitledMultiSelectEnumSchema";
|
||||
export type { McpElicitationUntitledSingleSelectEnumSchema } from "./McpElicitationUntitledSingleSelectEnumSchema";
|
||||
export type { McpServerElicitationAction } from "./McpServerElicitationAction";
|
||||
export type { McpServerElicitationRequestParams } from "./McpServerElicitationRequestParams";
|
||||
export type { McpServerElicitationRequestResponse } from "./McpServerElicitationRequestResponse";
|
||||
export type { McpServerOauthLoginCompletedNotification } from "./McpServerOauthLoginCompletedNotification";
|
||||
export type { McpServerOauthLoginParams } from "./McpServerOauthLoginParams";
|
||||
export type { McpServerOauthLoginResponse } from "./McpServerOauthLoginResponse";
|
||||
@@ -152,11 +126,6 @@ export type { PatchChangeKind } from "./PatchChangeKind";
|
||||
export type { PlanDeltaNotification } from "./PlanDeltaNotification";
|
||||
export type { PluginInstallParams } from "./PluginInstallParams";
|
||||
export type { PluginInstallResponse } from "./PluginInstallResponse";
|
||||
export type { PluginListParams } from "./PluginListParams";
|
||||
export type { PluginListResponse } from "./PluginListResponse";
|
||||
export type { PluginMarketplaceEntry } from "./PluginMarketplaceEntry";
|
||||
export type { PluginSource } from "./PluginSource";
|
||||
export type { PluginSummary } from "./PluginSummary";
|
||||
export type { ProductSurface } from "./ProductSurface";
|
||||
export type { ProfileV2 } from "./ProfileV2";
|
||||
export type { RateLimitSnapshot } from "./RateLimitSnapshot";
|
||||
|
||||
@@ -248,10 +248,6 @@ client_request_definitions! {
|
||||
params: v2::SkillsListParams,
|
||||
response: v2::SkillsListResponse,
|
||||
},
|
||||
PluginList => "plugin/list" {
|
||||
params: v2::PluginListParams,
|
||||
response: v2::PluginListResponse,
|
||||
},
|
||||
SkillsRemoteList => "skills/remote/list" {
|
||||
params: v2::SkillsRemoteReadParams,
|
||||
response: v2::SkillsRemoteReadResponse,
|
||||
@@ -654,12 +650,6 @@ server_request_definitions! {
|
||||
response: v2::ToolRequestUserInputResponse,
|
||||
},
|
||||
|
||||
/// Request input for an MCP server elicitation.
|
||||
McpServerElicitationRequest => "mcpServer/elicitation/request" {
|
||||
params: v2::McpServerElicitationRequestParams,
|
||||
response: v2::McpServerElicitationRequestResponse,
|
||||
},
|
||||
|
||||
/// Execute a dynamic tool call on the client.
|
||||
DynamicToolCall => "item/tool/call" {
|
||||
params: v2::DynamicToolCallParams,
|
||||
@@ -1056,63 +1046,6 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_mcp_server_elicitation_request() -> Result<()> {
|
||||
let requested_schema: v2::McpElicitationSchema = serde_json::from_value(json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"confirmed": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": ["confirmed"]
|
||||
}))?;
|
||||
let params = v2::McpServerElicitationRequestParams {
|
||||
thread_id: "thr_123".to_string(),
|
||||
turn_id: Some("turn_123".to_string()),
|
||||
server_name: "codex_apps".to_string(),
|
||||
request: v2::McpServerElicitationRequest::Form {
|
||||
meta: None,
|
||||
message: "Allow this request?".to_string(),
|
||||
requested_schema,
|
||||
},
|
||||
};
|
||||
let request = ServerRequest::McpServerElicitationRequest {
|
||||
request_id: RequestId::Integer(9),
|
||||
params: params.clone(),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
json!({
|
||||
"method": "mcpServer/elicitation/request",
|
||||
"id": 9,
|
||||
"params": {
|
||||
"threadId": "thr_123",
|
||||
"turnId": "turn_123",
|
||||
"serverName": "codex_apps",
|
||||
"mode": "form",
|
||||
"_meta": null,
|
||||
"message": "Allow this request?",
|
||||
"requestedSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"confirmed": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": ["confirmed"]
|
||||
}
|
||||
}
|
||||
}),
|
||||
serde_json::to_value(&request)?,
|
||||
);
|
||||
|
||||
let payload = ServerRequestPayload::McpServerElicitationRequest(params);
|
||||
assert_eq!(request.id(), &RequestId::Integer(9));
|
||||
assert_eq!(payload.request_with_id(RequestId::Integer(9)), request);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_get_account_rate_limits() -> Result<()> {
|
||||
let request = ClientRequest::GetAccountRateLimits {
|
||||
|
||||
@@ -183,7 +183,6 @@ impl ThreadHistoryBuilder {
|
||||
RolloutItem::Compacted(payload) => self.handle_compacted(payload),
|
||||
RolloutItem::TurnContext(_)
|
||||
| RolloutItem::SessionMeta(_)
|
||||
| RolloutItem::ForkReference(_)
|
||||
| RolloutItem::ResponseItem(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ use crate::RequestId;
|
||||
use crate::protocol::common::AuthMode;
|
||||
use codex_experimental_api_macros::ExperimentalApi;
|
||||
use codex_protocol::account::PlanType;
|
||||
use codex_protocol::approvals::ElicitationRequest as CoreElicitationRequest;
|
||||
use codex_protocol::approvals::ExecPolicyAmendment as CoreExecPolicyAmendment;
|
||||
use codex_protocol::approvals::NetworkApprovalContext as CoreNetworkApprovalContext;
|
||||
use codex_protocol::approvals::NetworkApprovalProtocol as CoreNetworkApprovalProtocol;
|
||||
@@ -28,9 +27,9 @@ use codex_protocol::mcp::Resource as McpResource;
|
||||
use codex_protocol::mcp::ResourceTemplate as McpResourceTemplate;
|
||||
use codex_protocol::mcp::Tool as McpTool;
|
||||
use codex_protocol::models::FileSystemPermissions as CoreFileSystemPermissions;
|
||||
use codex_protocol::models::MacOsAutomationPermission as CoreMacOsAutomationPermission;
|
||||
use codex_protocol::models::MacOsPreferencesPermission as CoreMacOsPreferencesPermission;
|
||||
use codex_protocol::models::MacOsSeatbeltProfileExtensions as CoreMacOsSeatbeltProfileExtensions;
|
||||
use codex_protocol::models::MacOsAutomationValue as CoreMacOsAutomationValue;
|
||||
use codex_protocol::models::MacOsPermissions as CoreMacOsPermissions;
|
||||
use codex_protocol::models::MacOsPreferencesValue as CoreMacOsPreferencesValue;
|
||||
use codex_protocol::models::MessagePhase;
|
||||
use codex_protocol::models::NetworkPermissions as CoreNetworkPermissions;
|
||||
use codex_protocol::models::PermissionProfile as CorePermissionProfile;
|
||||
@@ -629,6 +628,7 @@ pub struct NetworkRequirements {
|
||||
pub socks_port: Option<u16>,
|
||||
pub allow_upstream_proxy: Option<bool>,
|
||||
pub dangerously_allow_non_loopback_proxy: Option<bool>,
|
||||
pub dangerously_allow_non_loopback_admin: Option<bool>,
|
||||
pub dangerously_allow_all_unix_sockets: Option<bool>,
|
||||
pub allowed_domains: Option<Vec<String>>,
|
||||
pub denied_domains: Option<Vec<String>>,
|
||||
@@ -637,7 +637,7 @@ pub struct NetworkRequirements {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum ResidencyRequirement {
|
||||
Us,
|
||||
@@ -836,19 +836,19 @@ impl From<CoreFileSystemPermissions> for AdditionalFileSystemPermissions {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct AdditionalMacOsPermissions {
|
||||
pub preferences: CoreMacOsPreferencesPermission,
|
||||
pub automations: CoreMacOsAutomationPermission,
|
||||
pub accessibility: bool,
|
||||
pub calendar: bool,
|
||||
pub preferences: Option<CoreMacOsPreferencesValue>,
|
||||
pub automations: Option<CoreMacOsAutomationValue>,
|
||||
pub accessibility: Option<bool>,
|
||||
pub calendar: Option<bool>,
|
||||
}
|
||||
|
||||
impl From<CoreMacOsSeatbeltProfileExtensions> for AdditionalMacOsPermissions {
|
||||
fn from(value: CoreMacOsSeatbeltProfileExtensions) -> Self {
|
||||
impl From<CoreMacOsPermissions> for AdditionalMacOsPermissions {
|
||||
fn from(value: CoreMacOsPermissions) -> Self {
|
||||
Self {
|
||||
preferences: value.macos_preferences,
|
||||
automations: value.macos_automation,
|
||||
accessibility: value.macos_accessibility,
|
||||
calendar: value.macos_calendar,
|
||||
preferences: value.preferences,
|
||||
automations: value.automations,
|
||||
accessibility: value.accessibility,
|
||||
calendar: value.calendar,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1708,30 +1708,6 @@ pub struct AppInfo {
|
||||
/// ```
|
||||
#[serde(default = "default_enabled")]
|
||||
pub is_enabled: bool,
|
||||
#[serde(default)]
|
||||
pub plugin_display_names: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
/// EXPERIMENTAL - app metadata summary for plugin-install responses.
|
||||
pub struct AppSummary {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub install_url: Option<String>,
|
||||
}
|
||||
|
||||
impl From<AppInfo> for AppSummary {
|
||||
fn from(value: AppInfo) -> Self {
|
||||
Self {
|
||||
id: value.id,
|
||||
name: value.name,
|
||||
description: value.description,
|
||||
install_url: value.install_url,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
@@ -2392,23 +2368,6 @@ pub struct SkillsListResponse {
|
||||
pub data: Vec<SkillsListEntry>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct PluginListParams {
|
||||
/// Optional working directories used to discover repo marketplaces. When omitted,
|
||||
/// only home-scoped marketplaces are considered.
|
||||
#[ts(optional = nullable)]
|
||||
pub cwds: Option<Vec<AbsolutePathBuf>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct PluginListResponse {
|
||||
pub marketplaces: Vec<PluginMarketplaceEntry>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
@@ -2434,8 +2393,8 @@ pub enum HazelnutScope {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[ts(rename_all = "lowercase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum ProductSurface {
|
||||
Chatgpt,
|
||||
@@ -2572,34 +2531,6 @@ pub struct SkillsListEntry {
|
||||
pub errors: Vec<SkillErrorInfo>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct PluginMarketplaceEntry {
|
||||
pub name: String,
|
||||
pub path: PathBuf,
|
||||
pub plugins: Vec<PluginSummary>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct PluginSummary {
|
||||
pub name: String,
|
||||
pub source: PluginSource,
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[ts(tag = "type")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum PluginSource {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
Local { path: PathBuf },
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
@@ -2619,16 +2550,16 @@ pub struct SkillsConfigWriteResponse {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct PluginInstallParams {
|
||||
pub marketplace_path: AbsolutePathBuf,
|
||||
pub marketplace_name: String,
|
||||
pub plugin_name: String,
|
||||
#[ts(optional = nullable)]
|
||||
pub cwd: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct PluginInstallResponse {
|
||||
pub apps_needing_auth: Vec<AppSummary>,
|
||||
}
|
||||
pub struct PluginInstallResponse {}
|
||||
|
||||
impl From<CoreSkillMetadata> for SkillMetadata {
|
||||
fn from(value: CoreSkillMetadata) -> Self {
|
||||
@@ -4150,472 +4081,6 @@ pub struct FileChangeRequestApprovalResponse {
|
||||
pub decision: FileChangeApprovalDecision,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum McpServerElicitationAction {
|
||||
Accept,
|
||||
Decline,
|
||||
Cancel,
|
||||
}
|
||||
|
||||
impl McpServerElicitationAction {
|
||||
pub fn to_core(self) -> codex_protocol::approvals::ElicitationAction {
|
||||
match self {
|
||||
Self::Accept => codex_protocol::approvals::ElicitationAction::Accept,
|
||||
Self::Decline => codex_protocol::approvals::ElicitationAction::Decline,
|
||||
Self::Cancel => codex_protocol::approvals::ElicitationAction::Cancel,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<McpServerElicitationAction> for rmcp::model::ElicitationAction {
|
||||
fn from(value: McpServerElicitationAction) -> Self {
|
||||
match value {
|
||||
McpServerElicitationAction::Accept => Self::Accept,
|
||||
McpServerElicitationAction::Decline => Self::Decline,
|
||||
McpServerElicitationAction::Cancel => Self::Cancel,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<rmcp::model::ElicitationAction> for McpServerElicitationAction {
|
||||
fn from(value: rmcp::model::ElicitationAction) -> Self {
|
||||
match value {
|
||||
rmcp::model::ElicitationAction::Accept => Self::Accept,
|
||||
rmcp::model::ElicitationAction::Decline => Self::Decline,
|
||||
rmcp::model::ElicitationAction::Cancel => Self::Cancel,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpServerElicitationRequestParams {
|
||||
pub thread_id: String,
|
||||
/// Active Codex turn when this elicitation was observed, if app-server could correlate one.
|
||||
///
|
||||
/// This is nullable because MCP models elicitation as a standalone server-to-client request
|
||||
/// identified by the MCP server request id. It may be triggered during a turn, but turn
|
||||
/// context is app-server correlation rather than part of the protocol identity of the
|
||||
/// elicitation itself.
|
||||
pub turn_id: Option<String>,
|
||||
pub server_name: String,
|
||||
#[serde(flatten)]
|
||||
pub request: McpServerElicitationRequest,
|
||||
// TODO: When core can correlate an elicitation with an MCP tool call, expose the associated
|
||||
// McpToolCall item id here as an optional field. The current core event does not carry that
|
||||
// association.
|
||||
}
|
||||
|
||||
/// Typed form schema for MCP `elicitation/create` requests.
|
||||
///
|
||||
/// This matches the `requestedSchema` shape from the MCP 2025-11-25
|
||||
/// `ElicitRequestFormParams` schema.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpElicitationSchema {
|
||||
#[serde(rename = "$schema", skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional, rename = "$schema")]
|
||||
pub schema_uri: Option<String>,
|
||||
#[serde(rename = "type")]
|
||||
#[ts(rename = "type")]
|
||||
pub type_: McpElicitationObjectType,
|
||||
pub properties: BTreeMap<String, McpElicitationPrimitiveSchema>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub required: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum McpElicitationObjectType {
|
||||
Object,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(untagged)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum McpElicitationPrimitiveSchema {
|
||||
Enum(McpElicitationEnumSchema),
|
||||
String(McpElicitationStringSchema),
|
||||
Number(McpElicitationNumberSchema),
|
||||
Boolean(McpElicitationBooleanSchema),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpElicitationStringSchema {
|
||||
#[serde(rename = "type")]
|
||||
#[ts(rename = "type")]
|
||||
pub type_: McpElicitationStringType,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub title: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub description: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub min_length: Option<u32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub max_length: Option<u32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub format: Option<McpElicitationStringFormat>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub default: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum McpElicitationStringType {
|
||||
String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[ts(rename_all = "kebab-case", export_to = "v2/")]
|
||||
pub enum McpElicitationStringFormat {
|
||||
Email,
|
||||
Uri,
|
||||
Date,
|
||||
DateTime,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpElicitationNumberSchema {
|
||||
#[serde(rename = "type")]
|
||||
#[ts(rename = "type")]
|
||||
pub type_: McpElicitationNumberType,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub title: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub description: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub minimum: Option<f64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub maximum: Option<f64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub default: Option<f64>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum McpElicitationNumberType {
|
||||
Number,
|
||||
Integer,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpElicitationBooleanSchema {
|
||||
#[serde(rename = "type")]
|
||||
#[ts(rename = "type")]
|
||||
pub type_: McpElicitationBooleanType,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub title: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub description: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub default: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum McpElicitationBooleanType {
|
||||
Boolean,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(untagged)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum McpElicitationEnumSchema {
|
||||
SingleSelect(McpElicitationSingleSelectEnumSchema),
|
||||
MultiSelect(McpElicitationMultiSelectEnumSchema),
|
||||
Legacy(McpElicitationLegacyTitledEnumSchema),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpElicitationLegacyTitledEnumSchema {
|
||||
#[serde(rename = "type")]
|
||||
#[ts(rename = "type")]
|
||||
pub type_: McpElicitationStringType,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub title: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub description: Option<String>,
|
||||
#[serde(rename = "enum")]
|
||||
#[ts(rename = "enum")]
|
||||
pub enum_: Vec<String>,
|
||||
#[serde(rename = "enumNames", skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional, rename = "enumNames")]
|
||||
pub enum_names: Option<Vec<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub default: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(untagged)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum McpElicitationSingleSelectEnumSchema {
|
||||
Untitled(McpElicitationUntitledSingleSelectEnumSchema),
|
||||
Titled(McpElicitationTitledSingleSelectEnumSchema),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpElicitationUntitledSingleSelectEnumSchema {
|
||||
#[serde(rename = "type")]
|
||||
#[ts(rename = "type")]
|
||||
pub type_: McpElicitationStringType,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub title: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub description: Option<String>,
|
||||
#[serde(rename = "enum")]
|
||||
#[ts(rename = "enum")]
|
||||
pub enum_: Vec<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub default: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpElicitationTitledSingleSelectEnumSchema {
|
||||
#[serde(rename = "type")]
|
||||
#[ts(rename = "type")]
|
||||
pub type_: McpElicitationStringType,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub title: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub description: Option<String>,
|
||||
#[serde(rename = "oneOf")]
|
||||
#[ts(rename = "oneOf")]
|
||||
pub one_of: Vec<McpElicitationConstOption>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub default: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(untagged)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum McpElicitationMultiSelectEnumSchema {
|
||||
Untitled(McpElicitationUntitledMultiSelectEnumSchema),
|
||||
Titled(McpElicitationTitledMultiSelectEnumSchema),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpElicitationUntitledMultiSelectEnumSchema {
|
||||
#[serde(rename = "type")]
|
||||
#[ts(rename = "type")]
|
||||
pub type_: McpElicitationArrayType,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub title: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub description: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub min_items: Option<u64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub max_items: Option<u64>,
|
||||
pub items: McpElicitationUntitledEnumItems,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub default: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpElicitationTitledMultiSelectEnumSchema {
|
||||
#[serde(rename = "type")]
|
||||
#[ts(rename = "type")]
|
||||
pub type_: McpElicitationArrayType,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub title: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub description: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub min_items: Option<u64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub max_items: Option<u64>,
|
||||
pub items: McpElicitationTitledEnumItems,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub default: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum McpElicitationArrayType {
|
||||
Array,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpElicitationUntitledEnumItems {
|
||||
#[serde(rename = "type")]
|
||||
#[ts(rename = "type")]
|
||||
pub type_: McpElicitationStringType,
|
||||
#[serde(rename = "enum")]
|
||||
#[ts(rename = "enum")]
|
||||
pub enum_: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpElicitationTitledEnumItems {
|
||||
#[serde(rename = "anyOf", alias = "oneOf")]
|
||||
#[ts(rename = "anyOf")]
|
||||
pub any_of: Vec<McpElicitationConstOption>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpElicitationConstOption {
|
||||
#[serde(rename = "const")]
|
||||
#[ts(rename = "const")]
|
||||
pub const_: String,
|
||||
pub title: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(tag = "mode", rename_all = "camelCase")]
|
||||
#[ts(tag = "mode")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum McpServerElicitationRequest {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
Form {
|
||||
#[serde(rename = "_meta")]
|
||||
#[ts(rename = "_meta")]
|
||||
meta: Option<JsonValue>,
|
||||
message: String,
|
||||
requested_schema: McpElicitationSchema,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
Url {
|
||||
#[serde(rename = "_meta")]
|
||||
#[ts(rename = "_meta")]
|
||||
meta: Option<JsonValue>,
|
||||
message: String,
|
||||
url: String,
|
||||
elicitation_id: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl TryFrom<CoreElicitationRequest> for McpServerElicitationRequest {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_from(value: CoreElicitationRequest) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
CoreElicitationRequest::Form {
|
||||
meta,
|
||||
message,
|
||||
requested_schema,
|
||||
} => Ok(Self::Form {
|
||||
meta,
|
||||
message,
|
||||
requested_schema: serde_json::from_value(requested_schema)?,
|
||||
}),
|
||||
CoreElicitationRequest::Url {
|
||||
meta,
|
||||
message,
|
||||
url,
|
||||
elicitation_id,
|
||||
} => Ok(Self::Url {
|
||||
meta,
|
||||
message,
|
||||
url,
|
||||
elicitation_id,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpServerElicitationRequestResponse {
|
||||
pub action: McpServerElicitationAction,
|
||||
/// Structured user input for accepted elicitations, mirroring RMCP `CreateElicitationResult`.
|
||||
///
|
||||
/// This is nullable because decline/cancel responses have no content.
|
||||
pub content: Option<JsonValue>,
|
||||
/// Optional client metadata for form-mode action handling.
|
||||
#[serde(rename = "_meta")]
|
||||
#[ts(rename = "_meta")]
|
||||
pub meta: Option<JsonValue>,
|
||||
}
|
||||
|
||||
impl From<McpServerElicitationRequestResponse> for rmcp::model::CreateElicitationResult {
|
||||
fn from(value: McpServerElicitationRequestResponse) -> Self {
|
||||
Self {
|
||||
action: value.action.into(),
|
||||
content: value.content,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<rmcp::model::CreateElicitationResult> for McpServerElicitationRequestResponse {
|
||||
fn from(value: rmcp::model::CreateElicitationResult) -> Self {
|
||||
Self {
|
||||
action: value.action.into(),
|
||||
content: value.content,
|
||||
meta: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
@@ -4907,46 +4372,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_execution_request_approval_accepts_macos_automation_bundle_ids_object() {
|
||||
let params = serde_json::from_value::<CommandExecutionRequestApprovalParams>(json!({
|
||||
"threadId": "thr_123",
|
||||
"turnId": "turn_123",
|
||||
"itemId": "call_123",
|
||||
"command": "cat file",
|
||||
"cwd": "/tmp",
|
||||
"commandActions": null,
|
||||
"reason": null,
|
||||
"networkApprovalContext": null,
|
||||
"additionalPermissions": {
|
||||
"network": null,
|
||||
"fileSystem": null,
|
||||
"macos": {
|
||||
"preferences": "read_only",
|
||||
"automations": {
|
||||
"bundle_ids": ["com.apple.Notes"]
|
||||
},
|
||||
"accessibility": false,
|
||||
"calendar": false
|
||||
}
|
||||
},
|
||||
"proposedExecpolicyAmendment": null,
|
||||
"proposedNetworkPolicyAmendments": null,
|
||||
"availableDecisions": null
|
||||
}))
|
||||
.expect("bundle_ids object should deserialize");
|
||||
|
||||
assert_eq!(
|
||||
params
|
||||
.additional_permissions
|
||||
.and_then(|permissions| permissions.macos)
|
||||
.map(|macos| macos.automations),
|
||||
Some(CoreMacOsAutomationPermission::BundleIds(vec![
|
||||
"com.apple.Notes".to_string(),
|
||||
]))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sandbox_policy_round_trips_external_sandbox_network_access() {
|
||||
let v2_policy = SandboxPolicy::ExternalSandbox {
|
||||
@@ -4992,237 +4417,6 @@ mod tests {
|
||||
assert_eq!(back_to_v2, v2_policy);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mcp_server_elicitation_response_round_trips_rmcp_result() {
|
||||
let rmcp_result = rmcp::model::CreateElicitationResult {
|
||||
action: rmcp::model::ElicitationAction::Accept,
|
||||
content: Some(json!({
|
||||
"confirmed": true,
|
||||
})),
|
||||
};
|
||||
|
||||
let v2_response = McpServerElicitationRequestResponse::from(rmcp_result.clone());
|
||||
assert_eq!(
|
||||
v2_response,
|
||||
McpServerElicitationRequestResponse {
|
||||
action: McpServerElicitationAction::Accept,
|
||||
content: Some(json!({
|
||||
"confirmed": true,
|
||||
})),
|
||||
meta: None,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
rmcp::model::CreateElicitationResult::from(v2_response),
|
||||
rmcp_result
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mcp_server_elicitation_request_from_core_url_request() {
|
||||
let request = McpServerElicitationRequest::try_from(CoreElicitationRequest::Url {
|
||||
meta: None,
|
||||
message: "Finish sign-in".to_string(),
|
||||
url: "https://example.com/complete".to_string(),
|
||||
elicitation_id: "elicitation-123".to_string(),
|
||||
})
|
||||
.expect("URL request should convert");
|
||||
|
||||
assert_eq!(
|
||||
request,
|
||||
McpServerElicitationRequest::Url {
|
||||
meta: None,
|
||||
message: "Finish sign-in".to_string(),
|
||||
url: "https://example.com/complete".to_string(),
|
||||
elicitation_id: "elicitation-123".to_string(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mcp_server_elicitation_request_from_core_form_request() {
|
||||
let request = McpServerElicitationRequest::try_from(CoreElicitationRequest::Form {
|
||||
meta: None,
|
||||
message: "Allow this request?".to_string(),
|
||||
requested_schema: json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"confirmed": {
|
||||
"type": "boolean",
|
||||
}
|
||||
},
|
||||
"required": ["confirmed"],
|
||||
}),
|
||||
})
|
||||
.expect("form request should convert");
|
||||
|
||||
let expected_schema: McpElicitationSchema = serde_json::from_value(json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"confirmed": {
|
||||
"type": "boolean",
|
||||
}
|
||||
},
|
||||
"required": ["confirmed"],
|
||||
}))
|
||||
.expect("expected schema should deserialize");
|
||||
|
||||
assert_eq!(
|
||||
request,
|
||||
McpServerElicitationRequest::Form {
|
||||
meta: None,
|
||||
message: "Allow this request?".to_string(),
|
||||
requested_schema: expected_schema,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mcp_elicitation_schema_matches_mcp_2025_11_25_primitives() {
|
||||
let schema: McpElicitationSchema = serde_json::from_value(json!({
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string",
|
||||
"title": "Email",
|
||||
"description": "Work email address",
|
||||
"format": "email",
|
||||
"default": "dev@example.com",
|
||||
},
|
||||
"count": {
|
||||
"type": "integer",
|
||||
"title": "Count",
|
||||
"description": "How many items to create",
|
||||
"minimum": 1,
|
||||
"maximum": 5,
|
||||
"default": 3,
|
||||
},
|
||||
"confirmed": {
|
||||
"type": "boolean",
|
||||
"title": "Confirm",
|
||||
"description": "Approve the pending action",
|
||||
"default": true,
|
||||
},
|
||||
"legacyChoice": {
|
||||
"type": "string",
|
||||
"title": "Action",
|
||||
"description": "Legacy titled enum form",
|
||||
"enum": ["allow", "deny"],
|
||||
"enumNames": ["Allow", "Deny"],
|
||||
"default": "allow",
|
||||
},
|
||||
},
|
||||
"required": ["email", "confirmed"],
|
||||
}))
|
||||
.expect("schema should deserialize");
|
||||
|
||||
assert_eq!(
|
||||
schema,
|
||||
McpElicitationSchema {
|
||||
schema_uri: Some("https://json-schema.org/draft/2020-12/schema".to_string()),
|
||||
type_: McpElicitationObjectType::Object,
|
||||
properties: BTreeMap::from([
|
||||
(
|
||||
"confirmed".to_string(),
|
||||
McpElicitationPrimitiveSchema::Boolean(McpElicitationBooleanSchema {
|
||||
type_: McpElicitationBooleanType::Boolean,
|
||||
title: Some("Confirm".to_string()),
|
||||
description: Some("Approve the pending action".to_string()),
|
||||
default: Some(true),
|
||||
}),
|
||||
),
|
||||
(
|
||||
"count".to_string(),
|
||||
McpElicitationPrimitiveSchema::Number(McpElicitationNumberSchema {
|
||||
type_: McpElicitationNumberType::Integer,
|
||||
title: Some("Count".to_string()),
|
||||
description: Some("How many items to create".to_string()),
|
||||
minimum: Some(1.0),
|
||||
maximum: Some(5.0),
|
||||
default: Some(3.0),
|
||||
}),
|
||||
),
|
||||
(
|
||||
"email".to_string(),
|
||||
McpElicitationPrimitiveSchema::String(McpElicitationStringSchema {
|
||||
type_: McpElicitationStringType::String,
|
||||
title: Some("Email".to_string()),
|
||||
description: Some("Work email address".to_string()),
|
||||
min_length: None,
|
||||
max_length: None,
|
||||
format: Some(McpElicitationStringFormat::Email),
|
||||
default: Some("dev@example.com".to_string()),
|
||||
}),
|
||||
),
|
||||
(
|
||||
"legacyChoice".to_string(),
|
||||
McpElicitationPrimitiveSchema::Enum(McpElicitationEnumSchema::Legacy(
|
||||
McpElicitationLegacyTitledEnumSchema {
|
||||
type_: McpElicitationStringType::String,
|
||||
title: Some("Action".to_string()),
|
||||
description: Some("Legacy titled enum form".to_string()),
|
||||
enum_: vec!["allow".to_string(), "deny".to_string()],
|
||||
enum_names: Some(vec!["Allow".to_string(), "Deny".to_string(),]),
|
||||
default: Some("allow".to_string()),
|
||||
},
|
||||
)),
|
||||
),
|
||||
]),
|
||||
required: Some(vec!["email".to_string(), "confirmed".to_string()]),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mcp_server_elicitation_request_rejects_null_core_form_schema() {
|
||||
let result = McpServerElicitationRequest::try_from(CoreElicitationRequest::Form {
|
||||
meta: Some(json!({
|
||||
"persist": "session",
|
||||
})),
|
||||
message: "Allow this request?".to_string(),
|
||||
requested_schema: JsonValue::Null,
|
||||
});
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mcp_server_elicitation_request_rejects_invalid_core_form_schema() {
|
||||
let result = McpServerElicitationRequest::try_from(CoreElicitationRequest::Form {
|
||||
meta: None,
|
||||
message: "Allow this request?".to_string(),
|
||||
requested_schema: json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"confirmed": {
|
||||
"type": "object",
|
||||
}
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mcp_server_elicitation_response_serializes_nullable_content() {
|
||||
let response = McpServerElicitationRequestResponse {
|
||||
action: McpServerElicitationAction::Decline,
|
||||
content: None,
|
||||
meta: None,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
serde_json::to_value(response).expect("response should serialize"),
|
||||
json!({
|
||||
"action": "decline",
|
||||
"content": null,
|
||||
"_meta": null,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sandbox_policy_round_trips_workspace_write_read_only_access() {
|
||||
let readable_root = test_absolute_path();
|
||||
|
||||
@@ -69,7 +69,6 @@ core_test_support = { workspace = true }
|
||||
codex-utils-cargo-bin = { workspace = true }
|
||||
pretty_assertions = { workspace = true }
|
||||
rmcp = { workspace = true, default-features = false, features = [
|
||||
"elicitation",
|
||||
"server",
|
||||
"transport-streamable-http-server",
|
||||
] }
|
||||
|
||||
@@ -130,7 +130,7 @@ Example with notification opt-out:
|
||||
- `thread/status/changed` — notification emitted when a loaded thread’s status changes (`threadId` + new `status`).
|
||||
- `thread/archive` — move a thread’s rollout file into the archived directory; returns `{}` on success and emits `thread/archived`.
|
||||
- `thread/unsubscribe` — unsubscribe this connection from thread turn/item events. If this was the last subscriber, the server shuts down and unloads the thread, then emits `thread/closed`.
|
||||
- `thread/name/set` — set or update a thread’s user-facing name for either a loaded thread or a persisted rollout; returns `{}` on success and emits `thread/name/updated` to initialized, opted-in clients. Thread names are not required to be unique; name lookups resolve to the most recently updated thread.
|
||||
- `thread/name/set` — set or update a thread’s user-facing name for either a loaded thread or a persisted rollout; returns `{}` on success. Thread names are not required to be unique; name lookups resolve to the most recently updated thread.
|
||||
- `thread/unarchive` — move an archived rollout file back into the sessions directory; returns the restored `thread` on success and emits `thread/unarchived`.
|
||||
- `thread/compact/start` — trigger conversation history compaction for a thread; returns `{}` immediately while progress streams through standard turn/item notifications.
|
||||
- `thread/backgroundTerminals/clean` — terminate all running background terminals for a thread (experimental; requires `capabilities.experimentalApi`); returns `{}` when the cleanup request is accepted.
|
||||
@@ -148,13 +148,12 @@ Example with notification opt-out:
|
||||
- `experimentalFeature/list` — list feature flags with stage metadata (`beta`, `underDevelopment`, `stable`, etc.), enabled/default-enabled state, and cursor pagination. For non-beta flags, `displayName`/`description`/`announcement` are `null`.
|
||||
- `collaborationMode/list` — list available collaboration mode presets (experimental, no pagination). This response omits built-in developer instructions; clients should either pass `settings.developer_instructions: null` when setting a mode to use Codex's built-in instructions, or provide their own instructions explicitly.
|
||||
- `skills/list` — list skills for one or more `cwd` values (optional `forceReload`).
|
||||
- `plugin/list` — list discovered marketplaces reachable from optional `cwds` (unioned into a single list). When `cwds` is omitted, only home-scoped marketplaces are considered. Includes each plugin's current `enabled` state from config (**under development; do not call from production clients yet**).
|
||||
- `skills/changed` — notification emitted when watched local skill files change.
|
||||
- `skills/remote/list` — list public remote skills (**under development; do not call from production clients yet**).
|
||||
- `skills/remote/export` — download a remote skill by `hazelnutId` into `skills` under `codex_home` (**under development; do not call from production clients yet**).
|
||||
- `app/list` — list available apps.
|
||||
- `skills/config/write` — write user-level skill config by path.
|
||||
- `plugin/install` — install a plugin from a discovered marketplace entry by `pluginName` and `marketplacePath`; on success it returns `appsNeedingAuth` for any plugin-declared apps that still are not accessible in the current ChatGPT auth context (**under development; do not call from production clients yet**).
|
||||
- `plugin/install` — install a plugin from a discovered marketplace entry by `pluginName` and `marketplaceName` (**under development; do not call from production clients yet**).
|
||||
- `mcpServer/oauth/login` — start an OAuth login for a configured MCP server; returns an `authorization_url` and later emits `mcpServer/oauthLogin/completed` once the browser flow finishes.
|
||||
- `tool/requestUserInput` — prompt the user with 1–3 short questions for a tool call and return their answers (experimental).
|
||||
- `config/mcpServer/reload` — reload MCP server config from disk and queue a refresh for loaded threads (applied on each thread's next active turn); returns `{}`. Use this after editing `config.toml` without restarting the server.
|
||||
@@ -476,26 +475,6 @@ Invoke an app by including `$<app-slug>` in the text input and adding a `mention
|
||||
} } }
|
||||
```
|
||||
|
||||
### Example: Start a turn (invoke a plugin)
|
||||
|
||||
Invoke a plugin by including a UI mention token such as `@sample` in the text input and adding a `mention` input item with the exact `plugin://<plugin-name>@<marketplace-name>` path returned by `plugin/list`.
|
||||
|
||||
```json
|
||||
{ "method": "turn/start", "id": 35, "params": {
|
||||
"threadId": "thr_123",
|
||||
"input": [
|
||||
{ "type": "text", "text": "@sample Summarize the latest updates." },
|
||||
{ "type": "mention", "name": "Sample Plugin", "path": "plugin://sample@test" }
|
||||
]
|
||||
} }
|
||||
{ "id": 35, "result": { "turn": {
|
||||
"id": "turn_459",
|
||||
"status": "inProgress",
|
||||
"items": [],
|
||||
"error": null
|
||||
} } }
|
||||
```
|
||||
|
||||
### Example: Interrupt an active turn
|
||||
|
||||
You can cancel a running Turn with `turn/interrupt`.
|
||||
@@ -785,20 +764,6 @@ UI guidance for IDEs: surface an approval dialog as soon as the request arrives.
|
||||
|
||||
When the client responds to `item/tool/requestUserInput`, the server emits `serverRequest/resolved` with `{ threadId, requestId }`. If the pending request is cleared by turn start, turn completion, or turn interruption before the client answers, the server emits the same notification for that cleanup.
|
||||
|
||||
### MCP server elicitations
|
||||
|
||||
MCP servers can interrupt a turn and ask the client for structured input via `mcpServer/elicitation/request`.
|
||||
|
||||
Order of messages:
|
||||
|
||||
1. `mcpServer/elicitation/request` (request) — includes `threadId`, nullable `turnId`, `serverName`, and either:
|
||||
- a form request: `{ "mode": "form", "message": "...", "requestedSchema": { ... } }`
|
||||
- a URL request: `{ "mode": "url", "message": "...", "url": "...", "elicitationId": "..." }`
|
||||
2. Client response — `{ "action": "accept", "content": ... }`, `{ "action": "decline", "content": null }`, or `{ "action": "cancel", "content": null }`.
|
||||
3. `serverRequest/resolved` — `{ threadId, requestId }` confirms the pending request has been resolved or cleared, including lifecycle cleanup on turn start/complete/interrupt.
|
||||
|
||||
`turnId` is best-effort. When the elicitation is correlated with an active turn, the request includes that turn id; otherwise it is `null`.
|
||||
|
||||
### Dynamic tool calls (experimental)
|
||||
|
||||
`dynamicTools` on `thread/start` and the corresponding `item/tool/call` request/response flow are experimental APIs. To enable them, set `initialize.params.capabilities.experimentalApi = true`.
|
||||
@@ -996,7 +961,7 @@ The server also emits `app/list/updated` notifications whenever either source (a
|
||||
}
|
||||
```
|
||||
|
||||
Invoke an app by inserting `$<app-slug>` in the text input. The slug is derived from the app name and lowercased with non-alphanumeric characters replaced by `-` (for example, "Demo App" becomes `$demo-app`). Add a `mention` input item (recommended) so the server uses the exact `app://<connector-id>` path rather than guessing by name. Plugins use the same `mention` item shape, but with `plugin://<plugin-name>@<marketplace-name>` paths from `plugin/list`.
|
||||
Invoke an app by inserting `$<app-slug>` in the text input. The slug is derived from the app name and lowercased with non-alphanumeric characters replaced by `-` (for example, "Demo App" becomes `$demo-app`). Add a `mention` input item (recommended) so the server uses the exact `app://<connector-id>` path rather than guessing by name.
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
@@ -45,9 +45,6 @@ use codex_app_server_protocol::InterruptConversationResponse;
|
||||
use codex_app_server_protocol::ItemCompletedNotification;
|
||||
use codex_app_server_protocol::ItemStartedNotification;
|
||||
use codex_app_server_protocol::JSONRPCErrorError;
|
||||
use codex_app_server_protocol::McpServerElicitationAction;
|
||||
use codex_app_server_protocol::McpServerElicitationRequestParams;
|
||||
use codex_app_server_protocol::McpServerElicitationRequestResponse;
|
||||
use codex_app_server_protocol::McpToolCallError;
|
||||
use codex_app_server_protocol::McpToolCallResult;
|
||||
use codex_app_server_protocol::McpToolCallStatus;
|
||||
@@ -612,66 +609,6 @@ pub(crate) async fn apply_bespoke_event_handling(
|
||||
}
|
||||
}
|
||||
}
|
||||
EventMsg::ElicitationRequest(request) => {
|
||||
if matches!(api_version, ApiVersion::V2) {
|
||||
let permission_guard = thread_watch_manager
|
||||
.note_permission_requested(&conversation_id.to_string())
|
||||
.await;
|
||||
let turn_id = match request.turn_id.clone() {
|
||||
Some(turn_id) => Some(turn_id),
|
||||
None => {
|
||||
let state = thread_state.lock().await;
|
||||
state.active_turn_snapshot().map(|turn| turn.id)
|
||||
}
|
||||
};
|
||||
let server_name = request.server_name.clone();
|
||||
let request_body = match request.request.try_into() {
|
||||
Ok(request_body) => request_body,
|
||||
Err(err) => {
|
||||
error!(
|
||||
error = %err,
|
||||
server_name,
|
||||
request_id = ?request.id,
|
||||
"failed to parse typed MCP elicitation schema"
|
||||
);
|
||||
if let Err(err) = conversation
|
||||
.submit(Op::ResolveElicitation {
|
||||
server_name: request.server_name,
|
||||
request_id: request.id,
|
||||
decision: codex_protocol::approvals::ElicitationAction::Cancel,
|
||||
content: None,
|
||||
meta: None,
|
||||
})
|
||||
.await
|
||||
{
|
||||
error!("failed to submit ResolveElicitation: {err}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
let params = McpServerElicitationRequestParams {
|
||||
thread_id: conversation_id.to_string(),
|
||||
turn_id,
|
||||
server_name: request.server_name.clone(),
|
||||
request: request_body,
|
||||
};
|
||||
let (pending_request_id, rx) = outgoing
|
||||
.send_request(ServerRequestPayload::McpServerElicitationRequest(params))
|
||||
.await;
|
||||
tokio::spawn(async move {
|
||||
on_mcp_server_elicitation_response(
|
||||
request.server_name,
|
||||
request.id,
|
||||
pending_request_id,
|
||||
rx,
|
||||
conversation,
|
||||
thread_state,
|
||||
permission_guard,
|
||||
)
|
||||
.await;
|
||||
});
|
||||
}
|
||||
}
|
||||
EventMsg::DynamicToolCallRequest(request) => {
|
||||
if matches!(api_version, ApiVersion::V2) {
|
||||
let call_id = request.call_id;
|
||||
@@ -1590,9 +1527,7 @@ pub(crate) async fn apply_bespoke_event_handling(
|
||||
thread_name: thread_name_event.thread_name,
|
||||
};
|
||||
outgoing
|
||||
.send_global_server_notification(ServerNotification::ThreadNameUpdated(
|
||||
notification,
|
||||
))
|
||||
.send_server_notification(ServerNotification::ThreadNameUpdated(notification))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
@@ -2054,73 +1989,6 @@ async fn on_request_user_input_response(
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_mcp_server_elicitation_response(
|
||||
server_name: String,
|
||||
request_id: codex_protocol::mcp::RequestId,
|
||||
pending_request_id: RequestId,
|
||||
receiver: oneshot::Receiver<ClientRequestResult>,
|
||||
conversation: Arc<CodexThread>,
|
||||
thread_state: Arc<Mutex<ThreadState>>,
|
||||
permission_guard: ThreadWatchActiveGuard,
|
||||
) {
|
||||
let response = receiver.await;
|
||||
resolve_server_request_on_thread_listener(&thread_state, pending_request_id).await;
|
||||
drop(permission_guard);
|
||||
let response = mcp_server_elicitation_response_from_client_result(response);
|
||||
|
||||
if let Err(err) = conversation
|
||||
.submit(Op::ResolveElicitation {
|
||||
server_name,
|
||||
request_id,
|
||||
decision: response.action.to_core(),
|
||||
content: response.content,
|
||||
meta: response.meta,
|
||||
})
|
||||
.await
|
||||
{
|
||||
error!("failed to submit ResolveElicitation: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
fn mcp_server_elicitation_response_from_client_result(
|
||||
response: std::result::Result<ClientRequestResult, oneshot::error::RecvError>,
|
||||
) -> McpServerElicitationRequestResponse {
|
||||
match response {
|
||||
Ok(Ok(value)) => serde_json::from_value::<McpServerElicitationRequestResponse>(value)
|
||||
.unwrap_or_else(|err| {
|
||||
error!("failed to deserialize McpServerElicitationRequestResponse: {err}");
|
||||
McpServerElicitationRequestResponse {
|
||||
action: McpServerElicitationAction::Decline,
|
||||
content: None,
|
||||
meta: None,
|
||||
}
|
||||
}),
|
||||
Ok(Err(err)) if is_turn_transition_server_request_error(&err) => {
|
||||
McpServerElicitationRequestResponse {
|
||||
action: McpServerElicitationAction::Cancel,
|
||||
content: None,
|
||||
meta: None,
|
||||
}
|
||||
}
|
||||
Ok(Err(err)) => {
|
||||
error!("request failed with client error: {err:?}");
|
||||
McpServerElicitationRequestResponse {
|
||||
action: McpServerElicitationAction::Decline,
|
||||
content: None,
|
||||
meta: None,
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!("request failed: {err:?}");
|
||||
McpServerElicitationRequestResponse {
|
||||
action: McpServerElicitationAction::Decline,
|
||||
content: None,
|
||||
meta: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const REVIEW_FALLBACK_MESSAGE: &str = "Reviewer failed to output a response.";
|
||||
|
||||
fn render_review_output_text(output: &ReviewOutputEvent) -> String {
|
||||
@@ -2466,7 +2334,6 @@ mod tests {
|
||||
use anyhow::Result;
|
||||
use anyhow::anyhow;
|
||||
use anyhow::bail;
|
||||
use codex_app_server_protocol::JSONRPCErrorError;
|
||||
use codex_app_server_protocol::TurnPlanStepStatus;
|
||||
use codex_protocol::mcp::CallToolResult;
|
||||
use codex_protocol::plan_tool::PlanItemArg;
|
||||
@@ -2511,26 +2378,6 @@ mod tests {
|
||||
assert_eq!(completion_status, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mcp_server_elicitation_turn_transition_error_maps_to_cancel() {
|
||||
let error = JSONRPCErrorError {
|
||||
code: -1,
|
||||
message: "client request resolved because the turn state was changed".to_string(),
|
||||
data: Some(serde_json::json!({ "reason": "turnTransition" })),
|
||||
};
|
||||
|
||||
let response = mcp_server_elicitation_response_from_client_result(Ok(Err(error)));
|
||||
|
||||
assert_eq!(
|
||||
response,
|
||||
McpServerElicitationRequestResponse {
|
||||
action: McpServerElicitationAction::Cancel,
|
||||
content: None,
|
||||
meta: None,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collab_resume_begin_maps_to_item_started_resume_agent() {
|
||||
let event = CollabResumeBeginEvent {
|
||||
|
||||
@@ -22,7 +22,6 @@ use codex_app_server_protocol::AccountLoginCompletedNotification;
|
||||
use codex_app_server_protocol::AccountUpdatedNotification;
|
||||
use codex_app_server_protocol::AppInfo;
|
||||
use codex_app_server_protocol::AppListUpdatedNotification;
|
||||
use codex_app_server_protocol::AppSummary;
|
||||
use codex_app_server_protocol::AppsListParams;
|
||||
use codex_app_server_protocol::AppsListResponse;
|
||||
use codex_app_server_protocol::AskForApproval;
|
||||
@@ -80,11 +79,6 @@ use codex_app_server_protocol::ModelListParams;
|
||||
use codex_app_server_protocol::ModelListResponse;
|
||||
use codex_app_server_protocol::PluginInstallParams;
|
||||
use codex_app_server_protocol::PluginInstallResponse;
|
||||
use codex_app_server_protocol::PluginListParams;
|
||||
use codex_app_server_protocol::PluginListResponse;
|
||||
use codex_app_server_protocol::PluginMarketplaceEntry;
|
||||
use codex_app_server_protocol::PluginSource;
|
||||
use codex_app_server_protocol::PluginSummary;
|
||||
use codex_app_server_protocol::ProductSurface as ApiProductSurface;
|
||||
use codex_app_server_protocol::RequestId;
|
||||
use codex_app_server_protocol::ReviewDelivery as ApiReviewDelivery;
|
||||
@@ -188,8 +182,6 @@ use codex_core::config::edit::ConfigEdit;
|
||||
use codex_core::config::edit::ConfigEditsBuilder;
|
||||
use codex_core::config::types::McpServerTransportConfig;
|
||||
use codex_core::config_loader::CloudRequirementsLoader;
|
||||
use codex_core::connectors::filter_disallowed_connectors;
|
||||
use codex_core::connectors::merge_plugin_apps;
|
||||
use codex_core::default_client::set_default_client_residency_requirement;
|
||||
use codex_core::error::CodexErr;
|
||||
use codex_core::exec::ExecParams;
|
||||
@@ -206,15 +198,10 @@ use codex_core::mcp::collect_mcp_snapshot;
|
||||
use codex_core::mcp::group_tools_by_server;
|
||||
use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig;
|
||||
use codex_core::parse_cursor;
|
||||
use codex_core::plugins::AppConnectorId;
|
||||
use codex_core::plugins::MarketplaceError;
|
||||
use codex_core::plugins::MarketplacePluginSourceSummary;
|
||||
use codex_core::plugins::PluginInstallError as CorePluginInstallError;
|
||||
use codex_core::plugins::PluginInstallRequest;
|
||||
use codex_core::plugins::load_plugin_apps;
|
||||
use codex_core::read_head_for_summary;
|
||||
use codex_core::read_session_meta_line;
|
||||
use codex_core::resolve_fork_reference_rollout_path;
|
||||
use codex_core::rollout_date_parts;
|
||||
use codex_core::sandboxing::SandboxPermissions;
|
||||
use codex_core::skills::remote::export_remote_skill;
|
||||
@@ -474,14 +461,10 @@ impl CodexMessageProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
async fn load_latest_config(
|
||||
&self,
|
||||
fallback_cwd: Option<PathBuf>,
|
||||
) -> Result<Config, JSONRPCErrorError> {
|
||||
async fn load_latest_config(&self) -> Result<Config, JSONRPCErrorError> {
|
||||
let cloud_requirements = self.current_cloud_requirements();
|
||||
let mut config = codex_core::config::ConfigBuilder::default()
|
||||
.cli_overrides(self.cli_overrides.clone())
|
||||
.fallback_cwd(fallback_cwd)
|
||||
.cloud_requirements(cloud_requirements)
|
||||
.build()
|
||||
.await
|
||||
@@ -663,10 +646,6 @@ impl CodexMessageProcessor {
|
||||
self.skills_list(to_connection_request_id(request_id), params)
|
||||
.await;
|
||||
}
|
||||
ClientRequest::PluginList { request_id, params } => {
|
||||
self.plugin_list(to_connection_request_id(request_id), params)
|
||||
.await;
|
||||
}
|
||||
ClientRequest::SkillsRemoteList { request_id, params } => {
|
||||
self.skills_remote_list(to_connection_request_id(request_id), params)
|
||||
.await;
|
||||
@@ -2729,18 +2708,8 @@ impl CodexMessageProcessor {
|
||||
} else {
|
||||
read_summary_from_state_db_by_thread_id(&self.config, thread_uuid).await
|
||||
};
|
||||
let loaded_rollout_path = loaded_thread
|
||||
.as_ref()
|
||||
.and_then(|thread| thread.rollout_path());
|
||||
let mut rollout_path = db_summary.as_ref().map(|summary| summary.path.clone());
|
||||
if rollout_path.is_none()
|
||||
&& let Some(path) = loaded_rollout_path.as_ref()
|
||||
&& tokio::fs::try_exists(path).await.unwrap_or(false)
|
||||
{
|
||||
rollout_path = Some(path.clone());
|
||||
}
|
||||
let should_lookup_rollout = rollout_path.is_none() && loaded_thread.is_none();
|
||||
if should_lookup_rollout {
|
||||
if rollout_path.is_none() || include_turns {
|
||||
rollout_path =
|
||||
match find_thread_path_by_id_str(&self.config.codex_home, &thread_uuid.to_string())
|
||||
.await
|
||||
@@ -2801,6 +2770,7 @@ impl CodexMessageProcessor {
|
||||
return;
|
||||
};
|
||||
let config_snapshot = thread.config_snapshot().await;
|
||||
let loaded_rollout_path = thread.rollout_path();
|
||||
if include_turns && loaded_rollout_path.is_none() {
|
||||
self.send_invalid_request_error(
|
||||
request_id,
|
||||
@@ -3932,7 +3902,7 @@ impl CodexMessageProcessor {
|
||||
params: ExperimentalFeatureListParams,
|
||||
) {
|
||||
let ExperimentalFeatureListParams { cursor, limit } = params;
|
||||
let config = match self.load_latest_config(None).await {
|
||||
let config = match self.load_latest_config().await {
|
||||
Ok(config) => config,
|
||||
Err(error) => {
|
||||
self.outgoing.send_error(request_id, error).await;
|
||||
@@ -4047,7 +4017,7 @@ impl CodexMessageProcessor {
|
||||
}
|
||||
|
||||
async fn mcp_server_refresh(&self, request_id: ConnectionRequestId, _params: Option<()>) {
|
||||
let config = match self.load_latest_config(None).await {
|
||||
let config = match self.load_latest_config().await {
|
||||
Ok(config) => config,
|
||||
Err(error) => {
|
||||
self.outgoing.send_error(request_id, error).await;
|
||||
@@ -4106,7 +4076,7 @@ impl CodexMessageProcessor {
|
||||
request_id: ConnectionRequestId,
|
||||
params: McpServerOauthLoginParams,
|
||||
) {
|
||||
let config = match self.load_latest_config(None).await {
|
||||
let config = match self.load_latest_config().await {
|
||||
Ok(config) => config,
|
||||
Err(error) => {
|
||||
self.outgoing.send_error(request_id, error).await;
|
||||
@@ -4212,7 +4182,7 @@ impl CodexMessageProcessor {
|
||||
let request = request_id.clone();
|
||||
|
||||
let outgoing = Arc::clone(&self.outgoing);
|
||||
let config = match self.load_latest_config(None).await {
|
||||
let config = match self.load_latest_config().await {
|
||||
Ok(config) => config,
|
||||
Err(error) => {
|
||||
self.outgoing.send_error(request, error).await;
|
||||
@@ -4348,30 +4318,6 @@ impl CodexMessageProcessor {
|
||||
self.outgoing.send_error(request_id, error).await;
|
||||
}
|
||||
|
||||
async fn send_marketplace_error(
|
||||
&self,
|
||||
request_id: ConnectionRequestId,
|
||||
err: MarketplaceError,
|
||||
action: &str,
|
||||
) {
|
||||
match err {
|
||||
MarketplaceError::MarketplaceNotFound { .. } => {
|
||||
self.send_invalid_request_error(request_id, err.to_string())
|
||||
.await;
|
||||
}
|
||||
MarketplaceError::Io { .. } => {
|
||||
self.send_internal_error(request_id, format!("failed to {action}: {err}"))
|
||||
.await;
|
||||
}
|
||||
MarketplaceError::InvalidMarketplaceFile { .. }
|
||||
| MarketplaceError::PluginNotFound { .. }
|
||||
| MarketplaceError::InvalidPlugin(_) => {
|
||||
self.send_invalid_request_error(request_id, err.to_string())
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn wait_for_thread_shutdown(thread: &Arc<CodexThread>) -> ThreadShutdownResult {
|
||||
match thread.submit(Op::Shutdown).await {
|
||||
Ok(_) => {
|
||||
@@ -4635,7 +4581,7 @@ impl CodexMessageProcessor {
|
||||
}
|
||||
|
||||
async fn apps_list(&self, request_id: ConnectionRequestId, params: AppsListParams) {
|
||||
let mut config = match self.load_latest_config(None).await {
|
||||
let mut config = match self.load_latest_config().await {
|
||||
Ok(config) => config,
|
||||
Err(error) => {
|
||||
self.outgoing.send_error(request_id, error).await;
|
||||
@@ -4866,36 +4812,6 @@ impl CodexMessageProcessor {
|
||||
connectors::merge_connectors_with_accessible(all, accessible, all_connectors_loaded)
|
||||
}
|
||||
|
||||
fn plugin_apps_needing_auth(
|
||||
all_connectors: &[AppInfo],
|
||||
accessible_connectors: &[AppInfo],
|
||||
plugin_apps: &[AppConnectorId],
|
||||
codex_apps_ready: bool,
|
||||
) -> Vec<AppSummary> {
|
||||
if !codex_apps_ready {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let accessible_ids = accessible_connectors
|
||||
.iter()
|
||||
.map(|connector| connector.id.as_str())
|
||||
.collect::<HashSet<_>>();
|
||||
let plugin_app_ids = plugin_apps
|
||||
.iter()
|
||||
.map(|connector_id| connector_id.0.as_str())
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
all_connectors
|
||||
.iter()
|
||||
.filter(|connector| {
|
||||
plugin_app_ids.contains(connector.id.as_str())
|
||||
&& !accessible_ids.contains(connector.id.as_str())
|
||||
})
|
||||
.cloned()
|
||||
.map(AppSummary::from)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn should_send_app_list_updated_notification(
|
||||
connectors: &[AppInfo],
|
||||
accessible_loaded: bool,
|
||||
@@ -5008,66 +4924,6 @@ impl CodexMessageProcessor {
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn plugin_list(&self, request_id: ConnectionRequestId, params: PluginListParams) {
|
||||
let plugins_manager = self.thread_manager.plugins_manager();
|
||||
let roots = params.cwds.unwrap_or_default();
|
||||
|
||||
let config = match self.load_latest_config(None).await {
|
||||
Ok(config) => config,
|
||||
Err(err) => {
|
||||
self.outgoing.send_error(request_id, err).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let data = match tokio::task::spawn_blocking(move || {
|
||||
let marketplaces = plugins_manager.list_marketplaces_for_config(&config, &roots)?;
|
||||
Ok::<Vec<PluginMarketplaceEntry>, MarketplaceError>(
|
||||
marketplaces
|
||||
.into_iter()
|
||||
.map(|marketplace| PluginMarketplaceEntry {
|
||||
name: marketplace.name,
|
||||
path: marketplace.path,
|
||||
plugins: marketplace
|
||||
.plugins
|
||||
.into_iter()
|
||||
.map(|plugin| PluginSummary {
|
||||
enabled: plugin.enabled,
|
||||
name: plugin.name,
|
||||
source: match plugin.source {
|
||||
MarketplacePluginSourceSummary::Local { path } => {
|
||||
PluginSource::Local { path }
|
||||
}
|
||||
},
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(Ok(data)) => data,
|
||||
Ok(Err(err)) => {
|
||||
self.send_marketplace_error(request_id, err, "list marketplace plugins")
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
Err(err) => {
|
||||
self.send_internal_error(
|
||||
request_id,
|
||||
format!("failed to list marketplace plugins: {err}"),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
self.outgoing
|
||||
.send_response(request_id, PluginListResponse { marketplaces: data })
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn skills_remote_list(
|
||||
&self,
|
||||
request_id: ConnectionRequestId,
|
||||
@@ -5178,96 +5034,24 @@ impl CodexMessageProcessor {
|
||||
|
||||
async fn plugin_install(&self, request_id: ConnectionRequestId, params: PluginInstallParams) {
|
||||
let PluginInstallParams {
|
||||
marketplace_path,
|
||||
marketplace_name,
|
||||
plugin_name,
|
||||
cwd,
|
||||
} = params;
|
||||
let config_cwd = marketplace_path.as_path().parent().map(Path::to_path_buf);
|
||||
|
||||
let plugins_manager = self.thread_manager.plugins_manager();
|
||||
let request = PluginInstallRequest {
|
||||
plugin_name,
|
||||
marketplace_path,
|
||||
marketplace_name,
|
||||
cwd: cwd.unwrap_or_else(|| self.config.cwd.clone()),
|
||||
};
|
||||
|
||||
match plugins_manager.install_plugin(request).await {
|
||||
Ok(result) => {
|
||||
let config = match self.load_latest_config(config_cwd).await {
|
||||
Ok(config) => config,
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"failed to reload config after plugin install, using current config: {err:?}"
|
||||
);
|
||||
self.config.as_ref().clone()
|
||||
}
|
||||
};
|
||||
let plugin_apps = load_plugin_apps(&result.installed_path);
|
||||
let apps_needing_auth = if plugin_apps.is_empty()
|
||||
|| !config.features.enabled(Feature::Apps)
|
||||
{
|
||||
Vec::new()
|
||||
} else {
|
||||
let (all_connectors_result, accessible_connectors_result) = tokio::join!(
|
||||
connectors::list_all_connectors_with_options(&config, true),
|
||||
connectors::list_accessible_connectors_from_mcp_tools_with_options_and_status(
|
||||
&config, true
|
||||
),
|
||||
);
|
||||
|
||||
let all_connectors = match all_connectors_result {
|
||||
Ok(connectors) => filter_disallowed_connectors(merge_plugin_apps(
|
||||
connectors,
|
||||
plugin_apps.clone(),
|
||||
)),
|
||||
Err(err) => {
|
||||
warn!(
|
||||
plugin = result.plugin_id.as_key(),
|
||||
"failed to load app metadata after plugin install: {err:#}"
|
||||
);
|
||||
filter_disallowed_connectors(merge_plugin_apps(
|
||||
connectors::list_cached_all_connectors(&config)
|
||||
.await
|
||||
.unwrap_or_default(),
|
||||
plugin_apps.clone(),
|
||||
))
|
||||
}
|
||||
};
|
||||
let (accessible_connectors, codex_apps_ready) =
|
||||
match accessible_connectors_result {
|
||||
Ok(status) => (status.connectors, status.codex_apps_ready),
|
||||
Err(err) => {
|
||||
warn!(
|
||||
plugin = result.plugin_id.as_key(),
|
||||
"failed to load accessible apps after plugin install: {err:#}"
|
||||
);
|
||||
(
|
||||
connectors::list_cached_accessible_connectors_from_mcp_tools(
|
||||
&config,
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default(),
|
||||
false,
|
||||
)
|
||||
}
|
||||
};
|
||||
if !codex_apps_ready {
|
||||
warn!(
|
||||
plugin = result.plugin_id.as_key(),
|
||||
"codex_apps MCP not ready after plugin install; skipping appsNeedingAuth check"
|
||||
);
|
||||
}
|
||||
|
||||
Self::plugin_apps_needing_auth(
|
||||
&all_connectors,
|
||||
&accessible_connectors,
|
||||
&plugin_apps,
|
||||
codex_apps_ready,
|
||||
)
|
||||
};
|
||||
|
||||
Ok(_) => {
|
||||
plugins_manager.clear_cache();
|
||||
self.thread_manager.skills_manager().clear_cache();
|
||||
self.outgoing
|
||||
.send_response(request_id, PluginInstallResponse { apps_needing_auth })
|
||||
.send_response(request_id, PluginInstallResponse {})
|
||||
.await;
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -5278,10 +5062,6 @@ impl CodexMessageProcessor {
|
||||
}
|
||||
|
||||
match err {
|
||||
CorePluginInstallError::Marketplace(err) => {
|
||||
self.send_marketplace_error(request_id, err, "install plugin")
|
||||
.await;
|
||||
}
|
||||
CorePluginInstallError::Config(err) => {
|
||||
self.send_internal_error(
|
||||
request_id,
|
||||
@@ -5296,13 +5076,7 @@ impl CodexMessageProcessor {
|
||||
)
|
||||
.await;
|
||||
}
|
||||
CorePluginInstallError::Store(err) => {
|
||||
self.send_internal_error(
|
||||
request_id,
|
||||
format!("failed to install plugin: {err}"),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
CorePluginInstallError::Marketplace(_) | CorePluginInstallError::Store(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6719,14 +6493,6 @@ fn collect_resume_override_mismatches(
|
||||
config_snapshot.model_provider_id
|
||||
));
|
||||
}
|
||||
if let Some(requested_service_tier) = request.service_tier.as_ref()
|
||||
&& requested_service_tier != &config_snapshot.service_tier
|
||||
{
|
||||
mismatch_details.push(format!(
|
||||
"service_tier requested={requested_service_tier:?} active={:?}",
|
||||
config_snapshot.service_tier
|
||||
));
|
||||
}
|
||||
if let Some(requested_cwd) = request.cwd.as_deref() {
|
||||
let requested_cwd_path = std::path::PathBuf::from(requested_cwd);
|
||||
if requested_cwd_path != config_snapshot.cwd {
|
||||
@@ -7214,17 +6980,13 @@ pub(crate) async fn read_summary_from_rollout(
|
||||
.unwrap_or_else(|| fallback_provider.to_string());
|
||||
let git_info = git.as_ref().map(map_git_info);
|
||||
let updated_at = updated_at.or_else(|| timestamp.clone());
|
||||
let preview = read_rollout_items_from_rollout(path)
|
||||
.await
|
||||
.map(|items| preview_from_rollout_items(&items))
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok(ConversationSummary {
|
||||
conversation_id: session_meta.id,
|
||||
timestamp,
|
||||
updated_at,
|
||||
path: path.to_path_buf(),
|
||||
preview,
|
||||
preview: String::new(),
|
||||
model_provider,
|
||||
cwd: session_meta.cwd,
|
||||
cli_version: session_meta.cli_version,
|
||||
@@ -7242,7 +7004,7 @@ pub(crate) async fn read_rollout_items_from_rollout(
|
||||
InitialHistory::Resumed(resumed) => resumed.history,
|
||||
};
|
||||
|
||||
Ok(materialize_rollout_items_for_replay(codex_home_from_rollout_path(path), &items).await)
|
||||
Ok(items)
|
||||
}
|
||||
|
||||
fn extract_conversation_summary(
|
||||
@@ -7343,137 +7105,6 @@ fn preview_from_rollout_items(items: &[RolloutItem]) -> String {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn user_message_positions_in_rollout(items: &[RolloutItem]) -> Vec<usize> {
|
||||
let mut user_positions = Vec::new();
|
||||
for (idx, item) in items.iter().enumerate() {
|
||||
match item {
|
||||
RolloutItem::ResponseItem(item)
|
||||
if matches!(
|
||||
codex_core::parse_turn_item(item),
|
||||
Some(TurnItem::UserMessage(_))
|
||||
) =>
|
||||
{
|
||||
user_positions.push(idx);
|
||||
}
|
||||
RolloutItem::EventMsg(EventMsg::ThreadRolledBack(rollback)) => {
|
||||
let num_turns = usize::try_from(rollback.num_turns).unwrap_or(usize::MAX);
|
||||
let new_len = user_positions.len().saturating_sub(num_turns);
|
||||
user_positions.truncate(new_len);
|
||||
}
|
||||
RolloutItem::ResponseItem(_) => {}
|
||||
RolloutItem::SessionMeta(_)
|
||||
| RolloutItem::ForkReference(_)
|
||||
| RolloutItem::Compacted(_)
|
||||
| RolloutItem::TurnContext(_)
|
||||
| RolloutItem::EventMsg(_) => {}
|
||||
}
|
||||
}
|
||||
user_positions
|
||||
}
|
||||
|
||||
fn truncate_rollout_before_nth_user_message_from_start(
|
||||
items: &[RolloutItem],
|
||||
n_from_start: usize,
|
||||
) -> Vec<RolloutItem> {
|
||||
if n_from_start == usize::MAX {
|
||||
return items.to_vec();
|
||||
}
|
||||
|
||||
let user_positions = user_message_positions_in_rollout(items);
|
||||
if user_positions.len() <= n_from_start {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let cut_idx = user_positions[n_from_start];
|
||||
items[..cut_idx].to_vec()
|
||||
}
|
||||
|
||||
fn codex_home_from_rollout_path(path: &Path) -> Option<&Path> {
|
||||
path.ancestors().find_map(|ancestor| {
|
||||
let name = ancestor.file_name().and_then(OsStr::to_str)?;
|
||||
if name == codex_core::SESSIONS_SUBDIR || name == codex_core::ARCHIVED_SESSIONS_SUBDIR {
|
||||
ancestor.parent()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async fn materialize_rollout_items_for_replay(
|
||||
codex_home: Option<&Path>,
|
||||
rollout_items: &[RolloutItem],
|
||||
) -> Vec<RolloutItem> {
|
||||
const MAX_FORK_REFERENCE_DEPTH: usize = 8;
|
||||
|
||||
let mut materialized = Vec::new();
|
||||
let mut stack: Vec<(Vec<RolloutItem>, usize, usize)> = vec![(rollout_items.to_vec(), 0, 0)];
|
||||
|
||||
while let Some((items, mut idx, depth)) = stack.pop() {
|
||||
while idx < items.len() {
|
||||
match &items[idx] {
|
||||
RolloutItem::ForkReference(reference) => {
|
||||
if depth >= MAX_FORK_REFERENCE_DEPTH {
|
||||
warn!(
|
||||
"skipping fork reference recursion at depth {} for {:?}",
|
||||
depth, reference.rollout_path
|
||||
);
|
||||
idx += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
let resolved_rollout_path = if let Some(codex_home) = codex_home {
|
||||
match resolve_fork_reference_rollout_path(
|
||||
codex_home,
|
||||
&reference.rollout_path,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(path) => path,
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"failed to resolve fork reference rollout {:?}: {err}",
|
||||
reference.rollout_path
|
||||
);
|
||||
idx += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reference.rollout_path.clone()
|
||||
};
|
||||
let parent_history = match RolloutRecorder::get_rollout_history(
|
||||
&resolved_rollout_path,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(history) => history,
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"failed to load fork reference rollout {:?} (resolved from {:?}): {err}",
|
||||
resolved_rollout_path, reference.rollout_path
|
||||
);
|
||||
idx += 1;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let parent_items = truncate_rollout_before_nth_user_message_from_start(
|
||||
&parent_history.get_rollout_items(),
|
||||
reference.nth_user_message,
|
||||
);
|
||||
|
||||
stack.push((items, idx + 1, depth));
|
||||
stack.push((parent_items, 0, depth + 1));
|
||||
break;
|
||||
}
|
||||
item => materialized.push(item.clone()),
|
||||
}
|
||||
idx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
materialized
|
||||
}
|
||||
|
||||
fn with_thread_spawn_agent_metadata(
|
||||
source: codex_protocol::protocol::SessionSource,
|
||||
agent_nickname: Option<String>,
|
||||
@@ -7628,73 +7259,6 @@ mod tests {
|
||||
validate_dynamic_tools(&tools).expect("valid schema");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plugin_apps_needing_auth_returns_empty_when_codex_apps_is_not_ready() {
|
||||
let all_connectors = vec![AppInfo {
|
||||
id: "alpha".to_string(),
|
||||
name: "Alpha".to_string(),
|
||||
description: Some("Alpha connector".to_string()),
|
||||
logo_url: None,
|
||||
logo_url_dark: None,
|
||||
distribution_channel: None,
|
||||
branding: None,
|
||||
app_metadata: None,
|
||||
labels: None,
|
||||
install_url: Some("https://chatgpt.com/apps/alpha/alpha".to_string()),
|
||||
is_accessible: false,
|
||||
is_enabled: true,
|
||||
plugin_display_names: Vec::new(),
|
||||
}];
|
||||
|
||||
assert_eq!(
|
||||
CodexMessageProcessor::plugin_apps_needing_auth(
|
||||
&all_connectors,
|
||||
&[],
|
||||
&[AppConnectorId("alpha".to_string())],
|
||||
false,
|
||||
),
|
||||
Vec::<AppSummary>::new()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collect_resume_override_mismatches_includes_service_tier() {
|
||||
let request = ThreadResumeParams {
|
||||
thread_id: "thread-1".to_string(),
|
||||
history: None,
|
||||
path: None,
|
||||
model: None,
|
||||
model_provider: None,
|
||||
service_tier: Some(Some(codex_protocol::config_types::ServiceTier::Fast)),
|
||||
cwd: None,
|
||||
approval_policy: None,
|
||||
sandbox: None,
|
||||
config: None,
|
||||
base_instructions: None,
|
||||
developer_instructions: None,
|
||||
personality: None,
|
||||
persist_extended_history: false,
|
||||
};
|
||||
let config_snapshot = ThreadConfigSnapshot {
|
||||
model: "gpt-5".to_string(),
|
||||
model_provider_id: "openai".to_string(),
|
||||
service_tier: Some(codex_protocol::config_types::ServiceTier::Flex),
|
||||
approval_policy: codex_protocol::protocol::AskForApproval::OnRequest,
|
||||
sandbox_policy: codex_protocol::protocol::SandboxPolicy::DangerFullAccess,
|
||||
cwd: PathBuf::from("/tmp"),
|
||||
ephemeral: false,
|
||||
agent_use_function_call_inbox: false,
|
||||
reasoning_effort: None,
|
||||
personality: None,
|
||||
session_source: SessionSource::Cli,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
collect_resume_override_mismatches(&request, &config_snapshot),
|
||||
vec!["service_tier requested=Some(Fast) active=Some(Flex)".to_string()]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_conversation_summary_prefers_plain_user_messages() -> Result<()> {
|
||||
let conversation_id = ThreadId::from_string("3f941c35-29b3-493b-b0a4-e25800d9aeb0")?;
|
||||
|
||||
@@ -163,6 +163,7 @@ fn map_network_requirements_to_api(
|
||||
socks_port: network.socks_port,
|
||||
allow_upstream_proxy: network.allow_upstream_proxy,
|
||||
dangerously_allow_non_loopback_proxy: network.dangerously_allow_non_loopback_proxy,
|
||||
dangerously_allow_non_loopback_admin: network.dangerously_allow_non_loopback_admin,
|
||||
dangerously_allow_all_unix_sockets: network.dangerously_allow_all_unix_sockets,
|
||||
allowed_domains: network.allowed_domains,
|
||||
denied_domains: network.denied_domains,
|
||||
@@ -229,6 +230,7 @@ mod tests {
|
||||
socks_port: Some(1080),
|
||||
allow_upstream_proxy: Some(false),
|
||||
dangerously_allow_non_loopback_proxy: Some(false),
|
||||
dangerously_allow_non_loopback_admin: Some(false),
|
||||
dangerously_allow_all_unix_sockets: Some(true),
|
||||
allowed_domains: Some(vec!["api.openai.com".to_string()]),
|
||||
denied_domains: Some(vec!["example.com".to_string()]),
|
||||
@@ -273,6 +275,7 @@ mod tests {
|
||||
socks_port: Some(1080),
|
||||
allow_upstream_proxy: Some(false),
|
||||
dangerously_allow_non_loopback_proxy: Some(false),
|
||||
dangerously_allow_non_loopback_admin: Some(false),
|
||||
dangerously_allow_all_unix_sockets: Some(true),
|
||||
allowed_domains: Some(vec!["api.openai.com".to_string()]),
|
||||
denied_domains: Some(vec!["example.com".to_string()]),
|
||||
|
||||
@@ -38,6 +38,7 @@ use codex_core::ExecPolicyError;
|
||||
use codex_core::check_execpolicy_for_warnings;
|
||||
use codex_core::config_loader::ConfigLoadError;
|
||||
use codex_core::config_loader::TextRange as CoreTextRange;
|
||||
use codex_core::features::Feature;
|
||||
use codex_feedback::CodexFeedback;
|
||||
use codex_state::log_db;
|
||||
use tokio::sync::mpsc;
|
||||
@@ -123,25 +124,6 @@ enum ShutdownAction {
|
||||
Finish,
|
||||
}
|
||||
|
||||
async fn shutdown_signal() -> IoResult<()> {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use tokio::signal::unix::SignalKind;
|
||||
use tokio::signal::unix::signal;
|
||||
|
||||
let mut term = signal(SignalKind::terminate())?;
|
||||
tokio::select! {
|
||||
ctrl_c_result = tokio::signal::ctrl_c() => ctrl_c_result,
|
||||
_ = term.recv() => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
tokio::signal::ctrl_c().await
|
||||
}
|
||||
}
|
||||
|
||||
impl ShutdownState {
|
||||
fn requested(&self) -> bool {
|
||||
self.requested
|
||||
@@ -151,7 +133,7 @@ impl ShutdownState {
|
||||
self.forced
|
||||
}
|
||||
|
||||
fn on_signal(&mut self, connection_count: usize, running_turn_count: usize) {
|
||||
fn on_ctrl_c(&mut self, connection_count: usize, running_turn_count: usize) {
|
||||
if self.requested {
|
||||
self.forced = true;
|
||||
return;
|
||||
@@ -160,7 +142,7 @@ impl ShutdownState {
|
||||
self.requested = true;
|
||||
self.last_logged_running_turn_count = None;
|
||||
info!(
|
||||
"received shutdown signal; entering graceful restart drain (connections={}, runningAssistantTurns={}, requests still accepted until no assistant turns are running)",
|
||||
"received Ctrl-C; entering graceful restart drain (connections={}, runningAssistantTurns={}, requests still accepted until no assistant turns are running)",
|
||||
connection_count, running_turn_count,
|
||||
);
|
||||
}
|
||||
@@ -173,11 +155,11 @@ impl ShutdownState {
|
||||
if self.forced || running_turn_count == 0 {
|
||||
if self.forced {
|
||||
info!(
|
||||
"received second shutdown signal; forcing restart with {running_turn_count} running assistant turn(s) and {connection_count} connection(s)"
|
||||
"received second Ctrl-C; forcing restart with {running_turn_count} running assistant turn(s) and {connection_count} connection(s)"
|
||||
);
|
||||
} else {
|
||||
info!(
|
||||
"shutdown signal restart: no assistant turns running; stopping acceptor and disconnecting {connection_count} connection(s)"
|
||||
"Ctrl-C restart: no assistant turns running; stopping acceptor and disconnecting {connection_count} connection(s)"
|
||||
);
|
||||
}
|
||||
return ShutdownAction::Finish;
|
||||
@@ -185,7 +167,7 @@ impl ShutdownState {
|
||||
|
||||
if self.last_logged_running_turn_count != Some(running_turn_count) {
|
||||
info!(
|
||||
"shutdown signal restart: waiting for {running_turn_count} running assistant turn(s) to finish"
|
||||
"Ctrl-C restart: waiting for {running_turn_count} running assistant turn(s) to finish"
|
||||
);
|
||||
self.last_logged_running_turn_count = Some(running_turn_count);
|
||||
}
|
||||
@@ -377,7 +359,8 @@ pub async fn run_main_with_transport(
|
||||
};
|
||||
let single_client_mode = matches!(&transport_runtime, TransportRuntime::Stdio);
|
||||
let shutdown_when_no_connections = single_client_mode;
|
||||
let graceful_signal_restart_enabled = !single_client_mode;
|
||||
let graceful_ctrl_c_restart_enabled = !single_client_mode;
|
||||
|
||||
// Parse CLI overrides once and derive the base Config eagerly so later
|
||||
// components do not need to work with raw TOML values.
|
||||
let cli_kv_overrides = cli_config_overrides.parse_overrides().map_err(|e| {
|
||||
@@ -498,14 +481,18 @@ pub async fn run_main_with_transport(
|
||||
|
||||
let feedback_layer = feedback.logger_layer();
|
||||
let feedback_metadata_layer = feedback.metadata_layer();
|
||||
let log_db = codex_state::StateRuntime::init(
|
||||
config.sqlite_home.clone(),
|
||||
config.model_provider_id.clone(),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.ok()
|
||||
.map(log_db::start);
|
||||
let log_db = if config.features.enabled(Feature::Sqlite) {
|
||||
codex_state::StateRuntime::init(
|
||||
config.sqlite_home.clone(),
|
||||
config.model_provider_id.clone(),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.ok()
|
||||
.map(log_db::start)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let log_db_layer = log_db
|
||||
.clone()
|
||||
.map(|layer| layer.with_filter(Targets::new().with_default(Level::TRACE)));
|
||||
@@ -627,14 +614,14 @@ pub async fn run_main_with_transport(
|
||||
}
|
||||
|
||||
tokio::select! {
|
||||
shutdown_signal_result = shutdown_signal(), if graceful_signal_restart_enabled && !shutdown_state.forced() => {
|
||||
if let Err(err) = shutdown_signal_result {
|
||||
warn!("failed to listen for shutdown signal during graceful restart drain: {err}");
|
||||
ctrl_c_result = tokio::signal::ctrl_c(), if graceful_ctrl_c_restart_enabled && !shutdown_state.forced() => {
|
||||
if let Err(err) = ctrl_c_result {
|
||||
warn!("failed to listen for Ctrl-C during graceful restart drain: {err}");
|
||||
}
|
||||
let running_turn_count = *running_turn_count_rx.borrow();
|
||||
shutdown_state.on_signal(connections.len(), running_turn_count);
|
||||
shutdown_state.on_ctrl_c(connections.len(), running_turn_count);
|
||||
}
|
||||
changed = running_turn_count_rx.changed(), if graceful_signal_restart_enabled && shutdown_state.requested() => {
|
||||
changed = running_turn_count_rx.changed(), if graceful_ctrl_c_restart_enabled && shutdown_state.requested() => {
|
||||
if changed.is_err() {
|
||||
warn!("running-turn watcher closed during graceful restart drain");
|
||||
}
|
||||
|
||||
@@ -188,7 +188,6 @@ impl MessageProcessor {
|
||||
auth_manager.clone(),
|
||||
SessionSource::VSCode,
|
||||
config.model_catalog.clone(),
|
||||
config.custom_models.clone(),
|
||||
CollaborationModesConfig {
|
||||
default_mode_request_user_input: config
|
||||
.features
|
||||
|
||||
@@ -101,10 +101,6 @@ impl ThreadScopedOutgoingMessageSender {
|
||||
.await;
|
||||
}
|
||||
|
||||
pub(crate) async fn send_global_server_notification(&self, notification: ServerNotification) {
|
||||
self.outgoing.send_server_notification(notification).await;
|
||||
}
|
||||
|
||||
pub(crate) async fn abort_pending_server_requests(&self) {
|
||||
self.outgoing
|
||||
.cancel_requests_for_thread(
|
||||
|
||||
@@ -35,8 +35,6 @@ use codex_app_server_protocol::JSONRPCResponse;
|
||||
use codex_app_server_protocol::LoginAccountParams;
|
||||
use codex_app_server_protocol::MockExperimentalMethodParams;
|
||||
use codex_app_server_protocol::ModelListParams;
|
||||
use codex_app_server_protocol::PluginInstallParams;
|
||||
use codex_app_server_protocol::PluginListParams;
|
||||
use codex_app_server_protocol::RequestId;
|
||||
use codex_app_server_protocol::ReviewStartParams;
|
||||
use codex_app_server_protocol::ServerRequest;
|
||||
@@ -441,32 +439,6 @@ impl McpProcess {
|
||||
self.send_request("skills/list", params).await
|
||||
}
|
||||
|
||||
/// Send a `plugin/install` JSON-RPC request.
|
||||
pub async fn send_plugin_install_request(
|
||||
&mut self,
|
||||
params: PluginInstallParams,
|
||||
) -> anyhow::Result<i64> {
|
||||
let params = Some(serde_json::to_value(params)?);
|
||||
self.send_request("plugin/install", params).await
|
||||
}
|
||||
|
||||
/// Send a `plugin/list` JSON-RPC request.
|
||||
pub async fn send_plugin_list_request(
|
||||
&mut self,
|
||||
params: PluginListParams,
|
||||
) -> anyhow::Result<i64> {
|
||||
let params = Some(serde_json::to_value(params)?);
|
||||
self.send_request("plugin/list", params).await
|
||||
}
|
||||
|
||||
/// Send a JSON-RPC request with raw params for protocol-level validation tests.
|
||||
pub async fn send_raw_request(
|
||||
&mut self,
|
||||
method: &str,
|
||||
params: Option<serde_json::Value>,
|
||||
) -> anyhow::Result<i64> {
|
||||
self.send_request(method, params).await
|
||||
}
|
||||
/// Send a `collaborationMode/list` JSON-RPC request.
|
||||
pub async fn send_list_collaboration_modes_request(
|
||||
&mut self,
|
||||
|
||||
@@ -15,7 +15,6 @@ use std::path::Path;
|
||||
fn preset_to_info(preset: &ModelPreset, priority: i32) -> ModelInfo {
|
||||
ModelInfo {
|
||||
slug: preset.id.clone(),
|
||||
request_model: None,
|
||||
display_name: preset.display_name.clone(),
|
||||
description: Some(preset.description.clone()),
|
||||
default_reasoning_level: Some(preset.default_reasoning_effort),
|
||||
@@ -37,7 +36,6 @@ fn preset_to_info(preset: &ModelPreset, priority: i32) -> ModelInfo {
|
||||
default_verbosity: None,
|
||||
availability_nux: None,
|
||||
apply_patch_tool_type: None,
|
||||
web_search_tool_type: Default::default(),
|
||||
truncation_policy: TruncationPolicyConfig::bytes(10_000),
|
||||
supports_parallel_tool_calls: false,
|
||||
supports_image_detail_original: false,
|
||||
|
||||
@@ -97,7 +97,6 @@ async fn list_apps_uses_thread_feature_flag_when_thread_id_is_provided() -> Resu
|
||||
install_url: None,
|
||||
is_accessible: false,
|
||||
is_enabled: true,
|
||||
plugin_display_names: Vec::new(),
|
||||
}];
|
||||
let tools = vec![connector_tool("beta", "Beta App")?];
|
||||
let (server_url, server_handle) =
|
||||
@@ -200,7 +199,6 @@ async fn list_apps_reports_is_enabled_from_config() -> Result<()> {
|
||||
install_url: None,
|
||||
is_accessible: false,
|
||||
is_enabled: true,
|
||||
plugin_display_names: Vec::new(),
|
||||
}];
|
||||
let tools = vec![connector_tool("beta", "Beta App")?];
|
||||
let (server_url, server_handle) =
|
||||
@@ -310,7 +308,6 @@ async fn list_apps_emits_updates_and_returns_after_both_lists_load() -> Result<(
|
||||
install_url: None,
|
||||
is_accessible: false,
|
||||
is_enabled: true,
|
||||
plugin_display_names: Vec::new(),
|
||||
},
|
||||
AppInfo {
|
||||
id: "beta".to_string(),
|
||||
@@ -325,7 +322,6 @@ async fn list_apps_emits_updates_and_returns_after_both_lists_load() -> Result<(
|
||||
install_url: None,
|
||||
is_accessible: false,
|
||||
is_enabled: true,
|
||||
plugin_display_names: Vec::new(),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -374,7 +370,6 @@ async fn list_apps_emits_updates_and_returns_after_both_lists_load() -> Result<(
|
||||
install_url: Some("https://chatgpt.com/apps/beta-app/beta".to_string()),
|
||||
is_accessible: true,
|
||||
is_enabled: true,
|
||||
plugin_display_names: Vec::new(),
|
||||
}];
|
||||
|
||||
let first_update = read_app_list_updated_notification(&mut mcp).await?;
|
||||
@@ -394,7 +389,6 @@ async fn list_apps_emits_updates_and_returns_after_both_lists_load() -> Result<(
|
||||
install_url: Some("https://chatgpt.com/apps/beta/beta".to_string()),
|
||||
is_accessible: true,
|
||||
is_enabled: true,
|
||||
plugin_display_names: Vec::new(),
|
||||
},
|
||||
AppInfo {
|
||||
id: "alpha".to_string(),
|
||||
@@ -409,7 +403,6 @@ async fn list_apps_emits_updates_and_returns_after_both_lists_load() -> Result<(
|
||||
install_url: Some("https://chatgpt.com/apps/alpha/alpha".to_string()),
|
||||
is_accessible: false,
|
||||
is_enabled: true,
|
||||
plugin_display_names: Vec::new(),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -450,7 +443,6 @@ async fn list_apps_waits_for_accessible_data_before_emitting_directory_updates()
|
||||
install_url: None,
|
||||
is_accessible: false,
|
||||
is_enabled: true,
|
||||
plugin_display_names: Vec::new(),
|
||||
},
|
||||
AppInfo {
|
||||
id: "beta".to_string(),
|
||||
@@ -465,7 +457,6 @@ async fn list_apps_waits_for_accessible_data_before_emitting_directory_updates()
|
||||
install_url: None,
|
||||
is_accessible: false,
|
||||
is_enabled: true,
|
||||
plugin_display_names: Vec::new(),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -525,7 +516,6 @@ async fn list_apps_waits_for_accessible_data_before_emitting_directory_updates()
|
||||
install_url: Some("https://chatgpt.com/apps/beta/beta".to_string()),
|
||||
is_accessible: true,
|
||||
is_enabled: true,
|
||||
plugin_display_names: Vec::new(),
|
||||
},
|
||||
AppInfo {
|
||||
id: "alpha".to_string(),
|
||||
@@ -540,7 +530,6 @@ async fn list_apps_waits_for_accessible_data_before_emitting_directory_updates()
|
||||
install_url: Some("https://chatgpt.com/apps/alpha/alpha".to_string()),
|
||||
is_accessible: false,
|
||||
is_enabled: true,
|
||||
plugin_display_names: Vec::new(),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -575,7 +564,6 @@ async fn list_apps_does_not_emit_empty_interim_updates() -> Result<()> {
|
||||
install_url: None,
|
||||
is_accessible: false,
|
||||
is_enabled: true,
|
||||
plugin_display_names: Vec::new(),
|
||||
}];
|
||||
let (server_url, server_handle) = start_apps_server_with_delays(
|
||||
connectors.clone(),
|
||||
@@ -631,7 +619,6 @@ async fn list_apps_does_not_emit_empty_interim_updates() -> Result<()> {
|
||||
install_url: Some("https://chatgpt.com/apps/alpha/alpha".to_string()),
|
||||
is_accessible: false,
|
||||
is_enabled: true,
|
||||
plugin_display_names: Vec::new(),
|
||||
}];
|
||||
|
||||
let update = read_app_list_updated_notification(&mut mcp).await?;
|
||||
@@ -666,7 +653,6 @@ async fn list_apps_paginates_results() -> Result<()> {
|
||||
install_url: None,
|
||||
is_accessible: false,
|
||||
is_enabled: true,
|
||||
plugin_display_names: Vec::new(),
|
||||
},
|
||||
AppInfo {
|
||||
id: "beta".to_string(),
|
||||
@@ -681,7 +667,6 @@ async fn list_apps_paginates_results() -> Result<()> {
|
||||
install_url: None,
|
||||
is_accessible: false,
|
||||
is_enabled: true,
|
||||
plugin_display_names: Vec::new(),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -739,7 +724,6 @@ async fn list_apps_paginates_results() -> Result<()> {
|
||||
install_url: Some("https://chatgpt.com/apps/beta/beta".to_string()),
|
||||
is_accessible: true,
|
||||
is_enabled: true,
|
||||
plugin_display_names: Vec::new(),
|
||||
}];
|
||||
|
||||
assert_eq!(first_page, expected_first);
|
||||
@@ -783,7 +767,6 @@ async fn list_apps_paginates_results() -> Result<()> {
|
||||
install_url: Some("https://chatgpt.com/apps/alpha/alpha".to_string()),
|
||||
is_accessible: false,
|
||||
is_enabled: true,
|
||||
plugin_display_names: Vec::new(),
|
||||
}];
|
||||
|
||||
assert_eq!(second_page, expected_second);
|
||||
@@ -808,7 +791,6 @@ async fn list_apps_force_refetch_preserves_previous_cache_on_failure() -> Result
|
||||
install_url: None,
|
||||
is_accessible: false,
|
||||
is_enabled: true,
|
||||
plugin_display_names: Vec::new(),
|
||||
}];
|
||||
let tools = vec![connector_tool("beta", "Beta App")?];
|
||||
let (server_url, server_handle) =
|
||||
@@ -913,7 +895,6 @@ async fn list_apps_force_refetch_patches_updates_from_cached_snapshots() -> Resu
|
||||
install_url: None,
|
||||
is_accessible: false,
|
||||
is_enabled: true,
|
||||
plugin_display_names: Vec::new(),
|
||||
},
|
||||
AppInfo {
|
||||
id: "beta".to_string(),
|
||||
@@ -928,7 +909,6 @@ async fn list_apps_force_refetch_patches_updates_from_cached_snapshots() -> Resu
|
||||
install_url: None,
|
||||
is_accessible: false,
|
||||
is_enabled: true,
|
||||
plugin_display_names: Vec::new(),
|
||||
},
|
||||
];
|
||||
let initial_tools = vec![connector_tool("beta", "Beta App")?];
|
||||
@@ -978,7 +958,6 @@ async fn list_apps_force_refetch_patches_updates_from_cached_snapshots() -> Resu
|
||||
install_url: Some("https://chatgpt.com/apps/beta-app/beta".to_string()),
|
||||
is_accessible: true,
|
||||
is_enabled: true,
|
||||
plugin_display_names: Vec::new(),
|
||||
}]
|
||||
);
|
||||
|
||||
@@ -999,7 +978,6 @@ async fn list_apps_force_refetch_patches_updates_from_cached_snapshots() -> Resu
|
||||
install_url: Some("https://chatgpt.com/apps/beta-app/beta".to_string()),
|
||||
is_accessible: true,
|
||||
is_enabled: true,
|
||||
plugin_display_names: Vec::new(),
|
||||
},
|
||||
AppInfo {
|
||||
id: "alpha".to_string(),
|
||||
@@ -1014,7 +992,6 @@ async fn list_apps_force_refetch_patches_updates_from_cached_snapshots() -> Resu
|
||||
install_url: Some("https://chatgpt.com/apps/alpha/alpha".to_string()),
|
||||
is_accessible: false,
|
||||
is_enabled: true,
|
||||
plugin_display_names: Vec::new(),
|
||||
},
|
||||
]
|
||||
);
|
||||
@@ -1044,7 +1021,6 @@ async fn list_apps_force_refetch_patches_updates_from_cached_snapshots() -> Resu
|
||||
install_url: None,
|
||||
is_accessible: false,
|
||||
is_enabled: true,
|
||||
plugin_display_names: Vec::new(),
|
||||
}]);
|
||||
server_control.set_tools(Vec::new());
|
||||
|
||||
@@ -1074,7 +1050,6 @@ async fn list_apps_force_refetch_patches_updates_from_cached_snapshots() -> Resu
|
||||
install_url: Some("https://chatgpt.com/apps/beta-app/beta".to_string()),
|
||||
is_accessible: true,
|
||||
is_enabled: true,
|
||||
plugin_display_names: Vec::new(),
|
||||
},
|
||||
AppInfo {
|
||||
id: "alpha".to_string(),
|
||||
@@ -1089,7 +1064,6 @@ async fn list_apps_force_refetch_patches_updates_from_cached_snapshots() -> Resu
|
||||
install_url: Some("https://chatgpt.com/apps/alpha/alpha".to_string()),
|
||||
is_accessible: false,
|
||||
is_enabled: true,
|
||||
plugin_display_names: Vec::new(),
|
||||
},
|
||||
]
|
||||
);
|
||||
@@ -1117,7 +1091,6 @@ async fn list_apps_force_refetch_patches_updates_from_cached_snapshots() -> Resu
|
||||
install_url: Some("https://chatgpt.com/apps/alpha/alpha".to_string()),
|
||||
is_accessible: false,
|
||||
is_enabled: true,
|
||||
plugin_display_names: Vec::new(),
|
||||
}];
|
||||
let second_update = read_app_list_updated_notification(&mut mcp).await?;
|
||||
assert_eq!(second_update.data, expected_final);
|
||||
|
||||
@@ -6,7 +6,6 @@ use codex_app_server_protocol::ClientInfo;
|
||||
use codex_app_server_protocol::InitializeParams;
|
||||
use codex_app_server_protocol::JSONRPCError;
|
||||
use codex_app_server_protocol::JSONRPCMessage;
|
||||
use codex_app_server_protocol::JSONRPCNotification;
|
||||
use codex_app_server_protocol::JSONRPCRequest;
|
||||
use codex_app_server_protocol::JSONRPCResponse;
|
||||
use codex_app_server_protocol::RequestId;
|
||||
@@ -203,56 +202,6 @@ pub(super) async fn read_response_for_id(
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn read_notification_for_method(
|
||||
stream: &mut WsClient,
|
||||
method: &str,
|
||||
) -> Result<JSONRPCNotification> {
|
||||
loop {
|
||||
let message = read_jsonrpc_message(stream).await?;
|
||||
if let JSONRPCMessage::Notification(notification) = message
|
||||
&& notification.method == method
|
||||
{
|
||||
return Ok(notification);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn read_response_and_notification_for_method(
|
||||
stream: &mut WsClient,
|
||||
id: i64,
|
||||
method: &str,
|
||||
) -> Result<(JSONRPCResponse, JSONRPCNotification)> {
|
||||
let target_id = RequestId::Integer(id);
|
||||
let mut response = None;
|
||||
let mut notification = None;
|
||||
|
||||
while response.is_none() || notification.is_none() {
|
||||
let message = read_jsonrpc_message(stream).await?;
|
||||
match message {
|
||||
JSONRPCMessage::Response(candidate) if candidate.id == target_id => {
|
||||
response = Some(candidate);
|
||||
}
|
||||
JSONRPCMessage::Notification(candidate) if candidate.method == method => {
|
||||
if notification.replace(candidate).is_some() {
|
||||
bail!(
|
||||
"received duplicate notification for method `{method}` before completing paired read"
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let Some(response) = response else {
|
||||
bail!("response must be set before returning");
|
||||
};
|
||||
let Some(notification) = notification else {
|
||||
bail!("notification must be set before returning");
|
||||
};
|
||||
|
||||
Ok((response, notification))
|
||||
}
|
||||
|
||||
async fn read_error_for_id(stream: &mut WsClient, id: i64) -> Result<JSONRPCError> {
|
||||
let target_id = RequestId::Integer(id);
|
||||
loop {
|
||||
@@ -288,7 +237,7 @@ async fn read_jsonrpc_message(stream: &mut WsClient) -> Result<JSONRPCMessage> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn assert_no_message(stream: &mut WsClient, wait_for: Duration) -> Result<()> {
|
||||
async fn assert_no_message(stream: &mut WsClient, wait_for: Duration) -> Result<()> {
|
||||
match timeout(wait_for, stream.next()).await {
|
||||
Ok(Some(Ok(frame))) => bail!("unexpected frame while waiting for silence: {frame:?}"),
|
||||
Ok(Some(Err(err))) => bail!("unexpected websocket read error: {err}"),
|
||||
|
||||
@@ -83,57 +83,6 @@ async fn websocket_transport_second_ctrl_c_forces_exit_while_turn_running() -> R
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn websocket_transport_sigterm_waits_for_running_turn_before_exit() -> Result<()> {
|
||||
let GracefulCtrlCFixture {
|
||||
_codex_home,
|
||||
_server,
|
||||
mut process,
|
||||
mut ws,
|
||||
} = start_ctrl_c_restart_fixture(Duration::from_secs(3)).await?;
|
||||
|
||||
send_sigterm(&process)?;
|
||||
assert_process_does_not_exit_within(&mut process, Duration::from_millis(300)).await?;
|
||||
|
||||
let status = wait_for_process_exit_within(
|
||||
&mut process,
|
||||
Duration::from_secs(10),
|
||||
"timed out waiting for graceful SIGTERM restart shutdown",
|
||||
)
|
||||
.await?;
|
||||
assert!(status.success(), "expected graceful exit, got {status}");
|
||||
|
||||
expect_websocket_disconnect(&mut ws).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn websocket_transport_second_sigterm_forces_exit_while_turn_running() -> Result<()> {
|
||||
let GracefulCtrlCFixture {
|
||||
_codex_home,
|
||||
_server,
|
||||
mut process,
|
||||
mut ws,
|
||||
} = start_ctrl_c_restart_fixture(Duration::from_secs(3)).await?;
|
||||
|
||||
send_sigterm(&process)?;
|
||||
assert_process_does_not_exit_within(&mut process, Duration::from_millis(300)).await?;
|
||||
|
||||
send_sigterm(&process)?;
|
||||
let status = wait_for_process_exit_within(
|
||||
&mut process,
|
||||
Duration::from_secs(2),
|
||||
"timed out waiting for forced SIGTERM restart shutdown",
|
||||
)
|
||||
.await?;
|
||||
assert!(status.success(), "expected graceful exit, got {status}");
|
||||
|
||||
expect_websocket_disconnect(&mut ws).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct GracefulCtrlCFixture {
|
||||
_codex_home: TempDir,
|
||||
_server: wiremock::MockServer,
|
||||
@@ -231,24 +180,16 @@ async fn wait_for_responses_post(server: &wiremock::MockServer, wait_for: Durati
|
||||
}
|
||||
|
||||
fn send_sigint(process: &Child) -> Result<()> {
|
||||
send_signal(process, "-INT")
|
||||
}
|
||||
|
||||
fn send_sigterm(process: &Child) -> Result<()> {
|
||||
send_signal(process, "-TERM")
|
||||
}
|
||||
|
||||
fn send_signal(process: &Child, signal: &str) -> Result<()> {
|
||||
let pid = process
|
||||
.id()
|
||||
.context("websocket app-server process has no pid")?;
|
||||
let status = StdCommand::new("kill")
|
||||
.arg(signal)
|
||||
.arg("-INT")
|
||||
.arg(pid.to_string())
|
||||
.status()
|
||||
.with_context(|| format!("failed to invoke kill {signal}"))?;
|
||||
.context("failed to invoke kill -INT")?;
|
||||
if !status.success() {
|
||||
bail!("kill {signal} exited with {status}");
|
||||
bail!("kill -INT exited with {status}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -18,17 +18,11 @@ use core_test_support::fs_wait;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::Value;
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
use tempfile::TempDir;
|
||||
use tokio::time::timeout;
|
||||
|
||||
#[cfg(windows)]
|
||||
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(25);
|
||||
#[cfg(not(windows))]
|
||||
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
|
||||
#[cfg(windows)]
|
||||
const DEFAULT_NOTIFY_FILE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(25);
|
||||
#[cfg(not(windows))]
|
||||
const DEFAULT_NOTIFY_FILE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5);
|
||||
|
||||
#[tokio::test]
|
||||
async fn initialize_uses_client_info_name_as_originator() -> Result<()> {
|
||||
@@ -267,7 +261,7 @@ tmp_path.replace(payload_path)
|
||||
)
|
||||
.await??;
|
||||
|
||||
fs_wait::wait_for_path_exists(¬ify_file, DEFAULT_NOTIFY_FILE_TIMEOUT).await?;
|
||||
fs_wait::wait_for_path_exists(¬ify_file, Duration::from_secs(5)).await?;
|
||||
let payload_raw = tokio::fs::read_to_string(¬ify_file).await?;
|
||||
let payload: Value = serde_json::from_str(&payload_raw)?;
|
||||
assert_eq!(payload["client"], "xcode");
|
||||
|
||||
@@ -1,480 +0,0 @@
|
||||
use std::borrow::Cow;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use app_test_support::ChatGptAuthFixture;
|
||||
use app_test_support::McpProcess;
|
||||
use app_test_support::to_response;
|
||||
use app_test_support::write_chatgpt_auth;
|
||||
use axum::Json;
|
||||
use axum::Router;
|
||||
use axum::extract::State;
|
||||
use axum::http::HeaderMap;
|
||||
use axum::http::StatusCode;
|
||||
use axum::http::Uri;
|
||||
use axum::http::header::AUTHORIZATION;
|
||||
use axum::routing::get;
|
||||
use codex_app_server_protocol::JSONRPCMessage;
|
||||
use codex_app_server_protocol::JSONRPCResponse;
|
||||
use codex_app_server_protocol::McpElicitationSchema;
|
||||
use codex_app_server_protocol::McpServerElicitationAction;
|
||||
use codex_app_server_protocol::McpServerElicitationRequest;
|
||||
use codex_app_server_protocol::McpServerElicitationRequestParams;
|
||||
use codex_app_server_protocol::McpServerElicitationRequestResponse;
|
||||
use codex_app_server_protocol::RequestId;
|
||||
use codex_app_server_protocol::ServerRequest;
|
||||
use codex_app_server_protocol::ServerRequestResolvedNotification;
|
||||
use codex_app_server_protocol::ThreadStartParams;
|
||||
use codex_app_server_protocol::ThreadStartResponse;
|
||||
use codex_app_server_protocol::TurnCompletedNotification;
|
||||
use codex_app_server_protocol::TurnStartParams;
|
||||
use codex_app_server_protocol::TurnStartResponse;
|
||||
use codex_app_server_protocol::TurnStatus;
|
||||
use codex_app_server_protocol::UserInput as V2UserInput;
|
||||
use codex_core::auth::AuthCredentialsStoreMode;
|
||||
use core_test_support::responses;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rmcp::handler::server::ServerHandler;
|
||||
use rmcp::model::BooleanSchema;
|
||||
use rmcp::model::CallToolRequestParams;
|
||||
use rmcp::model::CallToolResult;
|
||||
use rmcp::model::Content;
|
||||
use rmcp::model::CreateElicitationRequestParams;
|
||||
use rmcp::model::ElicitationAction;
|
||||
use rmcp::model::ElicitationSchema;
|
||||
use rmcp::model::JsonObject;
|
||||
use rmcp::model::ListToolsResult;
|
||||
use rmcp::model::Meta;
|
||||
use rmcp::model::PrimitiveSchema;
|
||||
use rmcp::model::ServerCapabilities;
|
||||
use rmcp::model::ServerInfo;
|
||||
use rmcp::model::Tool;
|
||||
use rmcp::model::ToolAnnotations;
|
||||
use rmcp::service::RequestContext;
|
||||
use rmcp::service::RoleServer;
|
||||
use rmcp::transport::StreamableHttpServerConfig;
|
||||
use rmcp::transport::StreamableHttpService;
|
||||
use rmcp::transport::streamable_http_server::session::local::LocalSessionManager;
|
||||
use serde_json::Value;
|
||||
use serde_json::json;
|
||||
use tempfile::TempDir;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio::time::timeout;
|
||||
|
||||
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
|
||||
const CONNECTOR_ID: &str = "calendar";
|
||||
const CONNECTOR_NAME: &str = "Calendar";
|
||||
const TOOL_NAME: &str = "calendar_confirm_action";
|
||||
const QUALIFIED_TOOL_NAME: &str = "mcp__codex_apps__calendar_confirm_action";
|
||||
const TOOL_CALL_ID: &str = "call-calendar-confirm";
|
||||
const ELICITATION_MESSAGE: &str = "Allow this request?";
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
||||
async fn mcp_server_elicitation_round_trip() -> Result<()> {
|
||||
let responses_server = responses::start_mock_server().await;
|
||||
let tool_call_arguments = serde_json::to_string(&json!({}))?;
|
||||
let response_mock = responses::mount_sse_sequence(
|
||||
&responses_server,
|
||||
vec![
|
||||
responses::sse(vec![
|
||||
responses::ev_response_created("resp-0"),
|
||||
responses::ev_assistant_message("msg-0", "Warmup"),
|
||||
responses::ev_completed("resp-0"),
|
||||
]),
|
||||
responses::sse(vec![
|
||||
responses::ev_response_created("resp-1"),
|
||||
responses::ev_function_call(
|
||||
TOOL_CALL_ID,
|
||||
QUALIFIED_TOOL_NAME,
|
||||
&tool_call_arguments,
|
||||
),
|
||||
responses::ev_completed("resp-1"),
|
||||
]),
|
||||
responses::sse(vec![
|
||||
responses::ev_response_created("resp-2"),
|
||||
responses::ev_assistant_message("msg-1", "Done"),
|
||||
responses::ev_completed("resp-2"),
|
||||
]),
|
||||
],
|
||||
)
|
||||
.await;
|
||||
|
||||
let (apps_server_url, apps_server_handle) = start_apps_server().await?;
|
||||
|
||||
let codex_home = TempDir::new()?;
|
||||
write_config_toml(codex_home.path(), &responses_server.uri(), &apps_server_url)?;
|
||||
write_chatgpt_auth(
|
||||
codex_home.path(),
|
||||
ChatGptAuthFixture::new("chatgpt-token")
|
||||
.account_id("account-123")
|
||||
.chatgpt_user_id("user-123")
|
||||
.chatgpt_account_id("account-123"),
|
||||
AuthCredentialsStoreMode::File,
|
||||
)?;
|
||||
|
||||
let mut mcp = McpProcess::new(codex_home.path()).await?;
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let thread_start_id = mcp
|
||||
.send_thread_start_request(ThreadStartParams {
|
||||
model: Some("mock-model".to_string()),
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
let thread_start_resp: JSONRPCResponse = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(thread_start_id)),
|
||||
)
|
||||
.await??;
|
||||
let ThreadStartResponse { thread, .. } = to_response(thread_start_resp)?;
|
||||
|
||||
let warmup_turn_start_id = mcp
|
||||
.send_turn_start_request(TurnStartParams {
|
||||
thread_id: thread.id.clone(),
|
||||
input: vec![V2UserInput::Text {
|
||||
text: "Warm up connectors.".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
model: Some("mock-model".to_string()),
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
let warmup_turn_start_resp: JSONRPCResponse = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(warmup_turn_start_id)),
|
||||
)
|
||||
.await??;
|
||||
let _: TurnStartResponse = to_response(warmup_turn_start_resp)?;
|
||||
|
||||
let warmup_completed = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_notification_message("turn/completed"),
|
||||
)
|
||||
.await??;
|
||||
let warmup_completed: TurnCompletedNotification = serde_json::from_value(
|
||||
warmup_completed
|
||||
.params
|
||||
.clone()
|
||||
.expect("warmup turn/completed params"),
|
||||
)?;
|
||||
assert_eq!(warmup_completed.thread_id, thread.id);
|
||||
assert_eq!(warmup_completed.turn.status, TurnStatus::Completed);
|
||||
|
||||
let turn_start_id = mcp
|
||||
.send_turn_start_request(TurnStartParams {
|
||||
thread_id: thread.id.clone(),
|
||||
input: vec to run the calendar tool.".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
model: Some("mock-model".to_string()),
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
let turn_start_resp: JSONRPCResponse = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(turn_start_id)),
|
||||
)
|
||||
.await??;
|
||||
let TurnStartResponse { turn } = to_response(turn_start_resp)?;
|
||||
|
||||
let server_req = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_request_message(),
|
||||
)
|
||||
.await??;
|
||||
let ServerRequest::McpServerElicitationRequest { request_id, params } = server_req else {
|
||||
panic!("expected McpServerElicitationRequest request, got: {server_req:?}");
|
||||
};
|
||||
let requested_schema: McpElicitationSchema = serde_json::from_value(serde_json::to_value(
|
||||
ElicitationSchema::builder()
|
||||
.required_property("confirmed", PrimitiveSchema::Boolean(BooleanSchema::new()))
|
||||
.build()
|
||||
.map_err(anyhow::Error::msg)?,
|
||||
)?)?;
|
||||
|
||||
assert_eq!(
|
||||
params,
|
||||
McpServerElicitationRequestParams {
|
||||
thread_id: thread.id.clone(),
|
||||
turn_id: Some(turn.id.clone()),
|
||||
server_name: "codex_apps".to_string(),
|
||||
request: McpServerElicitationRequest::Form {
|
||||
meta: None,
|
||||
message: ELICITATION_MESSAGE.to_string(),
|
||||
requested_schema,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
let resolved_request_id = request_id.clone();
|
||||
mcp.send_response(
|
||||
request_id,
|
||||
serde_json::to_value(McpServerElicitationRequestResponse {
|
||||
action: McpServerElicitationAction::Accept,
|
||||
content: Some(json!({
|
||||
"confirmed": true,
|
||||
})),
|
||||
meta: None,
|
||||
})?,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut saw_resolved = false;
|
||||
loop {
|
||||
let message = timeout(DEFAULT_READ_TIMEOUT, mcp.read_next_message()).await??;
|
||||
let JSONRPCMessage::Notification(notification) = message else {
|
||||
continue;
|
||||
};
|
||||
|
||||
match notification.method.as_str() {
|
||||
"serverRequest/resolved" => {
|
||||
let resolved: ServerRequestResolvedNotification = serde_json::from_value(
|
||||
notification
|
||||
.params
|
||||
.clone()
|
||||
.expect("serverRequest/resolved params"),
|
||||
)?;
|
||||
assert_eq!(
|
||||
resolved,
|
||||
ServerRequestResolvedNotification {
|
||||
thread_id: thread.id.clone(),
|
||||
request_id: resolved_request_id.clone(),
|
||||
}
|
||||
);
|
||||
saw_resolved = true;
|
||||
}
|
||||
"turn/completed" => {
|
||||
let completed: TurnCompletedNotification = serde_json::from_value(
|
||||
notification.params.clone().expect("turn/completed params"),
|
||||
)?;
|
||||
assert!(saw_resolved, "serverRequest/resolved should arrive first");
|
||||
assert_eq!(completed.thread_id, thread.id);
|
||||
assert_eq!(completed.turn.id, turn.id);
|
||||
assert_eq!(completed.turn.status, TurnStatus::Completed);
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let requests = response_mock.requests();
|
||||
assert_eq!(requests.len(), 3);
|
||||
let function_call_output = requests[2].function_call_output(TOOL_CALL_ID);
|
||||
assert_eq!(
|
||||
function_call_output.get("type"),
|
||||
Some(&Value::String("function_call_output".to_string()))
|
||||
);
|
||||
assert_eq!(
|
||||
function_call_output.get("call_id"),
|
||||
Some(&Value::String(TOOL_CALL_ID.to_string()))
|
||||
);
|
||||
let output = function_call_output
|
||||
.get("output")
|
||||
.and_then(Value::as_str)
|
||||
.expect("function_call_output output should be a JSON string");
|
||||
assert_eq!(
|
||||
serde_json::from_str::<Value>(output)?,
|
||||
json!([{
|
||||
"type": "text",
|
||||
"text": "accepted"
|
||||
}])
|
||||
);
|
||||
|
||||
apps_server_handle.abort();
|
||||
let _ = apps_server_handle.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AppsServerState {
|
||||
expected_bearer: String,
|
||||
expected_account_id: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
struct ElicitationAppsMcpServer;
|
||||
|
||||
impl ServerHandler for ElicitationAppsMcpServer {
|
||||
fn get_info(&self) -> ServerInfo {
|
||||
ServerInfo {
|
||||
protocol_version: rmcp::model::ProtocolVersion::V_2025_06_18,
|
||||
capabilities: ServerCapabilities::builder().enable_tools().build(),
|
||||
..ServerInfo::default()
|
||||
}
|
||||
}
|
||||
|
||||
async fn list_tools(
|
||||
&self,
|
||||
_request: Option<rmcp::model::PaginatedRequestParams>,
|
||||
_context: RequestContext<RoleServer>,
|
||||
) -> Result<ListToolsResult, rmcp::ErrorData> {
|
||||
let input_schema: JsonObject = serde_json::from_value(json!({
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
}))
|
||||
.map_err(|err| rmcp::ErrorData::internal_error(err.to_string(), None))?;
|
||||
|
||||
let mut tool = Tool::new(
|
||||
Cow::Borrowed(TOOL_NAME),
|
||||
Cow::Borrowed("Confirm a calendar action."),
|
||||
Arc::new(input_schema),
|
||||
);
|
||||
tool.annotations = Some(ToolAnnotations::new().read_only(true));
|
||||
|
||||
let mut meta = Meta::new();
|
||||
meta.0
|
||||
.insert("connector_id".to_string(), json!(CONNECTOR_ID));
|
||||
meta.0
|
||||
.insert("connector_name".to_string(), json!(CONNECTOR_NAME));
|
||||
tool.meta = Some(meta);
|
||||
|
||||
Ok(ListToolsResult {
|
||||
tools: vec![tool],
|
||||
next_cursor: None,
|
||||
meta: None,
|
||||
})
|
||||
}
|
||||
|
||||
async fn call_tool(
|
||||
&self,
|
||||
_request: CallToolRequestParams,
|
||||
context: RequestContext<RoleServer>,
|
||||
) -> Result<CallToolResult, rmcp::ErrorData> {
|
||||
let requested_schema = ElicitationSchema::builder()
|
||||
.required_property("confirmed", PrimitiveSchema::Boolean(BooleanSchema::new()))
|
||||
.build()
|
||||
.map_err(|err| rmcp::ErrorData::internal_error(err.to_string(), None))?;
|
||||
|
||||
let result = context
|
||||
.peer
|
||||
.create_elicitation(CreateElicitationRequestParams::FormElicitationParams {
|
||||
meta: None,
|
||||
message: ELICITATION_MESSAGE.to_string(),
|
||||
requested_schema,
|
||||
})
|
||||
.await
|
||||
.map_err(|err| rmcp::ErrorData::internal_error(err.to_string(), None))?;
|
||||
|
||||
let output = match result.action {
|
||||
ElicitationAction::Accept => {
|
||||
assert_eq!(
|
||||
result.content,
|
||||
Some(json!({
|
||||
"confirmed": true,
|
||||
}))
|
||||
);
|
||||
"accepted"
|
||||
}
|
||||
ElicitationAction::Decline => "declined",
|
||||
ElicitationAction::Cancel => "cancelled",
|
||||
};
|
||||
|
||||
Ok(CallToolResult::success(vec![Content::text(output)]))
|
||||
}
|
||||
}
|
||||
|
||||
async fn start_apps_server() -> Result<(String, JoinHandle<()>)> {
|
||||
let state = Arc::new(AppsServerState {
|
||||
expected_bearer: "Bearer chatgpt-token".to_string(),
|
||||
expected_account_id: "account-123".to_string(),
|
||||
});
|
||||
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await?;
|
||||
let addr = listener.local_addr()?;
|
||||
|
||||
let mcp_service = StreamableHttpService::new(
|
||||
move || Ok(ElicitationAppsMcpServer),
|
||||
Arc::new(LocalSessionManager::default()),
|
||||
StreamableHttpServerConfig::default(),
|
||||
);
|
||||
|
||||
let router = Router::new()
|
||||
.route("/connectors/directory/list", get(list_directory_connectors))
|
||||
.route(
|
||||
"/connectors/directory/list_workspace",
|
||||
get(list_directory_connectors),
|
||||
)
|
||||
.with_state(state)
|
||||
.nest_service("/api/codex/apps", mcp_service);
|
||||
|
||||
let handle = tokio::spawn(async move {
|
||||
let _ = axum::serve(listener, router).await;
|
||||
});
|
||||
|
||||
Ok((format!("http://{addr}"), handle))
|
||||
}
|
||||
|
||||
async fn list_directory_connectors(
|
||||
State(state): State<Arc<AppsServerState>>,
|
||||
headers: HeaderMap,
|
||||
uri: Uri,
|
||||
) -> Result<Json<serde_json::Value>, StatusCode> {
|
||||
let bearer_ok = headers
|
||||
.get(AUTHORIZATION)
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.is_some_and(|value| value == state.expected_bearer);
|
||||
let account_ok = headers
|
||||
.get("chatgpt-account-id")
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.is_some_and(|value| value == state.expected_account_id);
|
||||
let external_logos_ok = uri
|
||||
.query()
|
||||
.is_some_and(|query| query.split('&').any(|pair| pair == "external_logos=true"));
|
||||
|
||||
if !bearer_ok || !account_ok {
|
||||
Err(StatusCode::UNAUTHORIZED)
|
||||
} else if !external_logos_ok {
|
||||
Err(StatusCode::BAD_REQUEST)
|
||||
} else {
|
||||
Ok(Json(json!({
|
||||
"apps": [{
|
||||
"id": CONNECTOR_ID,
|
||||
"name": CONNECTOR_NAME,
|
||||
"description": "Calendar connector",
|
||||
"logo_url": null,
|
||||
"logo_url_dark": null,
|
||||
"distribution_channel": null,
|
||||
"branding": null,
|
||||
"app_metadata": null,
|
||||
"labels": null,
|
||||
"install_url": null,
|
||||
"is_accessible": false,
|
||||
"is_enabled": true
|
||||
}],
|
||||
"next_token": null
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
fn write_config_toml(
|
||||
codex_home: &std::path::Path,
|
||||
responses_server_uri: &str,
|
||||
apps_server_url: &str,
|
||||
) -> std::io::Result<()> {
|
||||
std::fs::write(
|
||||
codex_home.join("config.toml"),
|
||||
format!(
|
||||
r#"
|
||||
model = "mock-model"
|
||||
approval_policy = "untrusted"
|
||||
sandbox_mode = "read-only"
|
||||
|
||||
model_provider = "mock_provider"
|
||||
chatgpt_base_url = "{apps_server_url}"
|
||||
mcp_oauth_credentials_store = "file"
|
||||
|
||||
[features]
|
||||
apps = true
|
||||
|
||||
[model_providers.mock_provider]
|
||||
name = "Mock provider for test"
|
||||
base_url = "{responses_server_uri}/v1"
|
||||
wire_api = "responses"
|
||||
request_max_retries = 0
|
||||
stream_max_retries = 0
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -11,12 +11,9 @@ mod dynamic_tools;
|
||||
mod experimental_api;
|
||||
mod experimental_feature_list;
|
||||
mod initialize;
|
||||
mod mcp_server_elicitation;
|
||||
mod model_list;
|
||||
mod output_schema;
|
||||
mod plan_item;
|
||||
mod plugin_install;
|
||||
mod plugin_list;
|
||||
mod rate_limits;
|
||||
mod realtime_conversation;
|
||||
mod request_user_input;
|
||||
@@ -28,7 +25,6 @@ mod thread_fork;
|
||||
mod thread_list;
|
||||
mod thread_loaded_list;
|
||||
mod thread_metadata_update;
|
||||
mod thread_name_websocket;
|
||||
mod thread_read;
|
||||
mod thread_resume;
|
||||
mod thread_rollback;
|
||||
|
||||
@@ -1,468 +0,0 @@
|
||||
use std::borrow::Cow;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex as StdMutex;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use app_test_support::ChatGptAuthFixture;
|
||||
use app_test_support::McpProcess;
|
||||
use app_test_support::to_response;
|
||||
use app_test_support::write_chatgpt_auth;
|
||||
use axum::Json;
|
||||
use axum::Router;
|
||||
use axum::extract::State;
|
||||
use axum::http::HeaderMap;
|
||||
use axum::http::StatusCode;
|
||||
use axum::http::Uri;
|
||||
use axum::http::header::AUTHORIZATION;
|
||||
use axum::routing::get;
|
||||
use codex_app_server_protocol::AppInfo;
|
||||
use codex_app_server_protocol::AppSummary;
|
||||
use codex_app_server_protocol::JSONRPCResponse;
|
||||
use codex_app_server_protocol::PluginInstallParams;
|
||||
use codex_app_server_protocol::PluginInstallResponse;
|
||||
use codex_app_server_protocol::RequestId;
|
||||
use codex_core::auth::AuthCredentialsStoreMode;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rmcp::handler::server::ServerHandler;
|
||||
use rmcp::model::JsonObject;
|
||||
use rmcp::model::ListToolsResult;
|
||||
use rmcp::model::Meta;
|
||||
use rmcp::model::ServerCapabilities;
|
||||
use rmcp::model::ServerInfo;
|
||||
use rmcp::model::Tool;
|
||||
use rmcp::model::ToolAnnotations;
|
||||
use rmcp::transport::StreamableHttpServerConfig;
|
||||
use rmcp::transport::StreamableHttpService;
|
||||
use rmcp::transport::streamable_http_server::session::local::LocalSessionManager;
|
||||
use serde_json::json;
|
||||
use tempfile::TempDir;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio::time::timeout;
|
||||
|
||||
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
|
||||
#[tokio::test]
|
||||
async fn plugin_install_rejects_relative_marketplace_paths() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let mut mcp = McpProcess::new(codex_home.path()).await?;
|
||||
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let request_id = mcp
|
||||
.send_raw_request(
|
||||
"plugin/install",
|
||||
Some(serde_json::json!({
|
||||
"marketplacePath": "relative-marketplace.json",
|
||||
"pluginName": "missing-plugin",
|
||||
})),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let err = timeout(
|
||||
DEFAULT_TIMEOUT,
|
||||
mcp.read_stream_until_error_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
|
||||
assert_eq!(err.error.code, -32600);
|
||||
assert!(err.error.message.contains("Invalid request"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn plugin_install_returns_invalid_request_for_missing_marketplace_file() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let mut mcp = McpProcess::new(codex_home.path()).await?;
|
||||
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let request_id = mcp
|
||||
.send_plugin_install_request(PluginInstallParams {
|
||||
marketplace_path: AbsolutePathBuf::try_from(
|
||||
codex_home.path().join("missing-marketplace.json"),
|
||||
)?,
|
||||
plugin_name: "missing-plugin".to_string(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
let err = timeout(
|
||||
DEFAULT_TIMEOUT,
|
||||
mcp.read_stream_until_error_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
|
||||
assert_eq!(err.error.code, -32600);
|
||||
assert!(err.error.message.contains("marketplace file"));
|
||||
assert!(err.error.message.contains("does not exist"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn plugin_install_returns_apps_needing_auth() -> Result<()> {
|
||||
let connectors = vec![
|
||||
AppInfo {
|
||||
id: "alpha".to_string(),
|
||||
name: "Alpha".to_string(),
|
||||
description: Some("Alpha connector".to_string()),
|
||||
logo_url: Some("https://example.com/alpha.png".to_string()),
|
||||
logo_url_dark: None,
|
||||
distribution_channel: Some("featured".to_string()),
|
||||
branding: None,
|
||||
app_metadata: None,
|
||||
labels: None,
|
||||
install_url: None,
|
||||
is_accessible: false,
|
||||
is_enabled: true,
|
||||
plugin_display_names: Vec::new(),
|
||||
},
|
||||
AppInfo {
|
||||
id: "beta".to_string(),
|
||||
name: "Beta".to_string(),
|
||||
description: Some("Beta connector".to_string()),
|
||||
logo_url: None,
|
||||
logo_url_dark: None,
|
||||
distribution_channel: None,
|
||||
branding: None,
|
||||
app_metadata: None,
|
||||
labels: None,
|
||||
install_url: None,
|
||||
is_accessible: false,
|
||||
is_enabled: true,
|
||||
plugin_display_names: Vec::new(),
|
||||
},
|
||||
];
|
||||
let tools = vec![connector_tool("beta", "Beta App")?];
|
||||
let (server_url, server_handle) = start_apps_server(connectors, tools).await?;
|
||||
|
||||
let codex_home = TempDir::new()?;
|
||||
write_connectors_config(codex_home.path(), &server_url)?;
|
||||
write_chatgpt_auth(
|
||||
codex_home.path(),
|
||||
ChatGptAuthFixture::new("chatgpt-token")
|
||||
.account_id("account-123")
|
||||
.chatgpt_user_id("user-123")
|
||||
.chatgpt_account_id("account-123"),
|
||||
AuthCredentialsStoreMode::File,
|
||||
)?;
|
||||
|
||||
let repo_root = TempDir::new()?;
|
||||
write_plugin_marketplace(
|
||||
repo_root.path(),
|
||||
"debug",
|
||||
"sample-plugin",
|
||||
"./sample-plugin",
|
||||
)?;
|
||||
write_plugin_source(repo_root.path(), "sample-plugin", &["alpha", "beta"])?;
|
||||
let marketplace_path =
|
||||
AbsolutePathBuf::try_from(repo_root.path().join(".agents/plugins/marketplace.json"))?;
|
||||
|
||||
let mut mcp = McpProcess::new(codex_home.path()).await?;
|
||||
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let request_id = mcp
|
||||
.send_plugin_install_request(PluginInstallParams {
|
||||
marketplace_path,
|
||||
plugin_name: "sample-plugin".to_string(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
let response: JSONRPCResponse = timeout(
|
||||
DEFAULT_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
let response: PluginInstallResponse = to_response(response)?;
|
||||
|
||||
assert_eq!(
|
||||
response,
|
||||
PluginInstallResponse {
|
||||
apps_needing_auth: vec![AppSummary {
|
||||
id: "alpha".to_string(),
|
||||
name: "Alpha".to_string(),
|
||||
description: Some("Alpha connector".to_string()),
|
||||
install_url: Some("https://chatgpt.com/apps/alpha/alpha".to_string()),
|
||||
}],
|
||||
}
|
||||
);
|
||||
|
||||
server_handle.abort();
|
||||
let _ = server_handle.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn plugin_install_filters_disallowed_apps_needing_auth() -> Result<()> {
|
||||
let connectors = vec![AppInfo {
|
||||
id: "alpha".to_string(),
|
||||
name: "Alpha".to_string(),
|
||||
description: Some("Alpha connector".to_string()),
|
||||
logo_url: Some("https://example.com/alpha.png".to_string()),
|
||||
logo_url_dark: None,
|
||||
distribution_channel: Some("featured".to_string()),
|
||||
branding: None,
|
||||
app_metadata: None,
|
||||
labels: None,
|
||||
install_url: None,
|
||||
is_accessible: false,
|
||||
is_enabled: true,
|
||||
plugin_display_names: Vec::new(),
|
||||
}];
|
||||
let (server_url, server_handle) = start_apps_server(connectors, Vec::new()).await?;
|
||||
|
||||
let codex_home = TempDir::new()?;
|
||||
write_connectors_config(codex_home.path(), &server_url)?;
|
||||
write_chatgpt_auth(
|
||||
codex_home.path(),
|
||||
ChatGptAuthFixture::new("chatgpt-token")
|
||||
.account_id("account-123")
|
||||
.chatgpt_user_id("user-123")
|
||||
.chatgpt_account_id("account-123"),
|
||||
AuthCredentialsStoreMode::File,
|
||||
)?;
|
||||
|
||||
let repo_root = TempDir::new()?;
|
||||
write_plugin_marketplace(
|
||||
repo_root.path(),
|
||||
"debug",
|
||||
"sample-plugin",
|
||||
"./sample-plugin",
|
||||
)?;
|
||||
write_plugin_source(
|
||||
repo_root.path(),
|
||||
"sample-plugin",
|
||||
&["alpha", "asdk_app_6938a94a61d881918ef32cb999ff937c"],
|
||||
)?;
|
||||
let marketplace_path =
|
||||
AbsolutePathBuf::try_from(repo_root.path().join(".agents/plugins/marketplace.json"))?;
|
||||
|
||||
let mut mcp = McpProcess::new(codex_home.path()).await?;
|
||||
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let request_id = mcp
|
||||
.send_plugin_install_request(PluginInstallParams {
|
||||
marketplace_path,
|
||||
plugin_name: "sample-plugin".to_string(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
let response: JSONRPCResponse = timeout(
|
||||
DEFAULT_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
let response: PluginInstallResponse = to_response(response)?;
|
||||
|
||||
assert_eq!(
|
||||
response,
|
||||
PluginInstallResponse {
|
||||
apps_needing_auth: vec![AppSummary {
|
||||
id: "alpha".to_string(),
|
||||
name: "Alpha".to_string(),
|
||||
description: Some("Alpha connector".to_string()),
|
||||
install_url: Some("https://chatgpt.com/apps/alpha/alpha".to_string()),
|
||||
}],
|
||||
}
|
||||
);
|
||||
|
||||
server_handle.abort();
|
||||
let _ = server_handle.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AppsServerState {
|
||||
response: Arc<StdMutex<serde_json::Value>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct PluginInstallMcpServer {
|
||||
tools: Arc<StdMutex<Vec<Tool>>>,
|
||||
}
|
||||
|
||||
impl ServerHandler for PluginInstallMcpServer {
|
||||
fn get_info(&self) -> ServerInfo {
|
||||
ServerInfo {
|
||||
capabilities: ServerCapabilities::builder().enable_tools().build(),
|
||||
..ServerInfo::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn list_tools(
|
||||
&self,
|
||||
_request: Option<rmcp::model::PaginatedRequestParams>,
|
||||
_context: rmcp::service::RequestContext<rmcp::service::RoleServer>,
|
||||
) -> impl std::future::Future<Output = Result<ListToolsResult, rmcp::ErrorData>> + Send + '_
|
||||
{
|
||||
let tools = self.tools.clone();
|
||||
async move {
|
||||
let tools = tools
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.clone();
|
||||
Ok(ListToolsResult {
|
||||
tools,
|
||||
next_cursor: None,
|
||||
meta: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn start_apps_server(
|
||||
connectors: Vec<AppInfo>,
|
||||
tools: Vec<Tool>,
|
||||
) -> Result<(String, JoinHandle<()>)> {
|
||||
let state = Arc::new(AppsServerState {
|
||||
response: Arc::new(StdMutex::new(
|
||||
json!({ "apps": connectors, "next_token": null }),
|
||||
)),
|
||||
});
|
||||
let tools = Arc::new(StdMutex::new(tools));
|
||||
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await?;
|
||||
let addr = listener.local_addr()?;
|
||||
let mcp_service = StreamableHttpService::new(
|
||||
{
|
||||
let tools = tools.clone();
|
||||
move || {
|
||||
Ok(PluginInstallMcpServer {
|
||||
tools: tools.clone(),
|
||||
})
|
||||
}
|
||||
},
|
||||
Arc::new(LocalSessionManager::default()),
|
||||
StreamableHttpServerConfig::default(),
|
||||
);
|
||||
let router = Router::new()
|
||||
.route("/connectors/directory/list", get(list_directory_connectors))
|
||||
.route(
|
||||
"/connectors/directory/list_workspace",
|
||||
get(list_directory_connectors),
|
||||
)
|
||||
.with_state(state)
|
||||
.nest_service("/api/codex/apps", mcp_service);
|
||||
|
||||
let handle = tokio::spawn(async move {
|
||||
let _ = axum::serve(listener, router).await;
|
||||
});
|
||||
|
||||
Ok((format!("http://{addr}"), handle))
|
||||
}
|
||||
|
||||
async fn list_directory_connectors(
|
||||
State(state): State<Arc<AppsServerState>>,
|
||||
headers: HeaderMap,
|
||||
uri: Uri,
|
||||
) -> Result<impl axum::response::IntoResponse, StatusCode> {
|
||||
let bearer_ok = headers
|
||||
.get(AUTHORIZATION)
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.is_some_and(|value| value == "Bearer chatgpt-token");
|
||||
let account_ok = headers
|
||||
.get("chatgpt-account-id")
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.is_some_and(|value| value == "account-123");
|
||||
let external_logos_ok = uri
|
||||
.query()
|
||||
.is_some_and(|query| query.split('&').any(|pair| pair == "external_logos=true"));
|
||||
|
||||
if !bearer_ok || !account_ok {
|
||||
Err(StatusCode::UNAUTHORIZED)
|
||||
} else if !external_logos_ok {
|
||||
Err(StatusCode::BAD_REQUEST)
|
||||
} else {
|
||||
let response = state
|
||||
.response
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.clone();
|
||||
Ok(Json(response))
|
||||
}
|
||||
}
|
||||
|
||||
fn connector_tool(connector_id: &str, connector_name: &str) -> Result<Tool> {
|
||||
let schema: JsonObject = serde_json::from_value(json!({
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
}))?;
|
||||
let mut tool = Tool::new(
|
||||
Cow::Owned(format!("connector_{connector_id}")),
|
||||
Cow::Borrowed("Connector test tool"),
|
||||
Arc::new(schema),
|
||||
);
|
||||
tool.annotations = Some(ToolAnnotations::new().read_only(true));
|
||||
|
||||
let mut meta = Meta::new();
|
||||
meta.0
|
||||
.insert("connector_id".to_string(), json!(connector_id));
|
||||
meta.0
|
||||
.insert("connector_name".to_string(), json!(connector_name));
|
||||
tool.meta = Some(meta);
|
||||
Ok(tool)
|
||||
}
|
||||
|
||||
fn write_connectors_config(codex_home: &std::path::Path, base_url: &str) -> std::io::Result<()> {
|
||||
std::fs::write(
|
||||
codex_home.join("config.toml"),
|
||||
format!(
|
||||
r#"
|
||||
chatgpt_base_url = "{base_url}"
|
||||
mcp_oauth_credentials_store = "file"
|
||||
|
||||
[features]
|
||||
connectors = true
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn write_plugin_marketplace(
|
||||
repo_root: &std::path::Path,
|
||||
marketplace_name: &str,
|
||||
plugin_name: &str,
|
||||
source_path: &str,
|
||||
) -> std::io::Result<()> {
|
||||
std::fs::create_dir_all(repo_root.join(".git"))?;
|
||||
std::fs::create_dir_all(repo_root.join(".agents/plugins"))?;
|
||||
std::fs::write(
|
||||
repo_root.join(".agents/plugins/marketplace.json"),
|
||||
format!(
|
||||
r#"{{
|
||||
"name": "{marketplace_name}",
|
||||
"plugins": [
|
||||
{{
|
||||
"name": "{plugin_name}",
|
||||
"source": {{
|
||||
"source": "local",
|
||||
"path": "{source_path}"
|
||||
}}
|
||||
}}
|
||||
]
|
||||
}}"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn write_plugin_source(
|
||||
repo_root: &std::path::Path,
|
||||
plugin_name: &str,
|
||||
app_ids: &[&str],
|
||||
) -> Result<()> {
|
||||
let plugin_root = repo_root.join(".agents/plugins").join(plugin_name);
|
||||
std::fs::create_dir_all(plugin_root.join(".codex-plugin"))?;
|
||||
std::fs::write(
|
||||
plugin_root.join(".codex-plugin/plugin.json"),
|
||||
format!(r#"{{"name":"{plugin_name}"}}"#),
|
||||
)?;
|
||||
|
||||
let apps = app_ids
|
||||
.iter()
|
||||
.map(|app_id| ((*app_id).to_string(), json!({ "id": app_id })))
|
||||
.collect::<serde_json::Map<_, _>>();
|
||||
std::fs::write(
|
||||
plugin_root.join(".app.json"),
|
||||
serde_json::to_vec_pretty(&json!({ "apps": apps }))?,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,299 +0,0 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use app_test_support::McpProcess;
|
||||
use app_test_support::to_response;
|
||||
use codex_app_server_protocol::JSONRPCResponse;
|
||||
use codex_app_server_protocol::PluginListParams;
|
||||
use codex_app_server_protocol::PluginListResponse;
|
||||
use codex_app_server_protocol::RequestId;
|
||||
use codex_core::config::set_project_trust_level;
|
||||
use codex_protocol::config_types::TrustLevel;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tempfile::TempDir;
|
||||
use tokio::time::timeout;
|
||||
|
||||
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
|
||||
#[tokio::test]
|
||||
async fn plugin_list_returns_invalid_request_for_invalid_marketplace_file() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let repo_root = TempDir::new()?;
|
||||
std::fs::create_dir_all(repo_root.path().join(".git"))?;
|
||||
std::fs::create_dir_all(repo_root.path().join(".agents/plugins"))?;
|
||||
std::fs::write(
|
||||
repo_root.path().join(".agents/plugins/marketplace.json"),
|
||||
"{not json",
|
||||
)?;
|
||||
|
||||
let home = codex_home.path().to_string_lossy().into_owned();
|
||||
let mut mcp = McpProcess::new_with_env(
|
||||
codex_home.path(),
|
||||
&[
|
||||
("HOME", Some(home.as_str())),
|
||||
("USERPROFILE", Some(home.as_str())),
|
||||
],
|
||||
)
|
||||
.await?;
|
||||
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let request_id = mcp
|
||||
.send_plugin_list_request(PluginListParams {
|
||||
cwds: Some(vec![AbsolutePathBuf::try_from(repo_root.path())?]),
|
||||
})
|
||||
.await?;
|
||||
|
||||
let err = timeout(
|
||||
DEFAULT_TIMEOUT,
|
||||
mcp.read_stream_until_error_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
|
||||
assert_eq!(err.error.code, -32600);
|
||||
assert!(err.error.message.contains("invalid marketplace file"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn plugin_list_rejects_relative_cwds() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let mut mcp = McpProcess::new(codex_home.path()).await?;
|
||||
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let request_id = mcp
|
||||
.send_raw_request(
|
||||
"plugin/list",
|
||||
Some(serde_json::json!({
|
||||
"cwds": ["relative-root"],
|
||||
})),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let err = timeout(
|
||||
DEFAULT_TIMEOUT,
|
||||
mcp.read_stream_until_error_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
|
||||
assert_eq!(err.error.code, -32600);
|
||||
assert!(err.error.message.contains("Invalid request"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn plugin_list_accepts_omitted_cwds() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
std::fs::create_dir_all(codex_home.path().join(".agents/plugins"))?;
|
||||
std::fs::write(
|
||||
codex_home.path().join(".agents/plugins/marketplace.json"),
|
||||
r#"{
|
||||
"name": "codex-curated",
|
||||
"plugins": [
|
||||
{
|
||||
"name": "home-plugin",
|
||||
"source": {
|
||||
"source": "local",
|
||||
"path": "./home-plugin"
|
||||
}
|
||||
}
|
||||
]
|
||||
}"#,
|
||||
)?;
|
||||
let home = codex_home.path().to_string_lossy().into_owned();
|
||||
let mut mcp = McpProcess::new_with_env(
|
||||
codex_home.path(),
|
||||
&[
|
||||
("HOME", Some(home.as_str())),
|
||||
("USERPROFILE", Some(home.as_str())),
|
||||
],
|
||||
)
|
||||
.await?;
|
||||
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let request_id = mcp
|
||||
.send_plugin_list_request(PluginListParams { cwds: None })
|
||||
.await?;
|
||||
|
||||
let response: JSONRPCResponse = timeout(
|
||||
DEFAULT_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
let _: PluginListResponse = to_response(response)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn plugin_list_includes_enabled_state_from_config() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let repo_root = TempDir::new()?;
|
||||
std::fs::create_dir_all(repo_root.path().join(".git"))?;
|
||||
std::fs::create_dir_all(repo_root.path().join(".agents/plugins"))?;
|
||||
std::fs::write(
|
||||
repo_root.path().join(".agents/plugins/marketplace.json"),
|
||||
r#"{
|
||||
"name": "codex-curated",
|
||||
"plugins": [
|
||||
{
|
||||
"name": "enabled-plugin",
|
||||
"source": {
|
||||
"source": "local",
|
||||
"path": "./enabled-plugin"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "disabled-plugin",
|
||||
"source": {
|
||||
"source": "local",
|
||||
"path": "./disabled-plugin"
|
||||
}
|
||||
}
|
||||
]
|
||||
}"#,
|
||||
)?;
|
||||
std::fs::write(
|
||||
codex_home.path().join("config.toml"),
|
||||
r#"[features]
|
||||
plugins = true
|
||||
|
||||
[plugins."enabled-plugin@codex-curated"]
|
||||
enabled = true
|
||||
|
||||
[plugins."disabled-plugin@codex-curated"]
|
||||
enabled = false
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let mut mcp = McpProcess::new(codex_home.path()).await?;
|
||||
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let request_id = mcp
|
||||
.send_plugin_list_request(PluginListParams {
|
||||
cwds: Some(vec![AbsolutePathBuf::try_from(repo_root.path())?]),
|
||||
})
|
||||
.await?;
|
||||
|
||||
let response: JSONRPCResponse = timeout(
|
||||
DEFAULT_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
let response: PluginListResponse = to_response(response)?;
|
||||
|
||||
let marketplace = response
|
||||
.marketplaces
|
||||
.into_iter()
|
||||
.find(|marketplace| {
|
||||
marketplace.path == repo_root.path().join(".agents/plugins/marketplace.json")
|
||||
})
|
||||
.expect("expected repo marketplace entry");
|
||||
|
||||
assert_eq!(marketplace.name, "codex-curated");
|
||||
assert_eq!(marketplace.plugins.len(), 2);
|
||||
assert_eq!(marketplace.plugins[0].name, "enabled-plugin");
|
||||
assert_eq!(marketplace.plugins[0].enabled, true);
|
||||
assert_eq!(marketplace.plugins[1].name, "disabled-plugin");
|
||||
assert_eq!(marketplace.plugins[1].enabled, false);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn plugin_list_uses_home_config_for_enabled_state() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
std::fs::create_dir_all(codex_home.path().join(".agents/plugins"))?;
|
||||
std::fs::write(
|
||||
codex_home.path().join(".agents/plugins/marketplace.json"),
|
||||
r#"{
|
||||
"name": "codex-curated",
|
||||
"plugins": [
|
||||
{
|
||||
"name": "shared-plugin",
|
||||
"source": {
|
||||
"source": "local",
|
||||
"path": "./shared-plugin"
|
||||
}
|
||||
}
|
||||
]
|
||||
}"#,
|
||||
)?;
|
||||
std::fs::write(
|
||||
codex_home.path().join("config.toml"),
|
||||
r#"[features]
|
||||
plugins = true
|
||||
|
||||
[plugins."shared-plugin@codex-curated"]
|
||||
enabled = true
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let workspace_enabled = TempDir::new()?;
|
||||
std::fs::create_dir_all(workspace_enabled.path().join(".git"))?;
|
||||
std::fs::create_dir_all(workspace_enabled.path().join(".agents/plugins"))?;
|
||||
std::fs::write(
|
||||
workspace_enabled
|
||||
.path()
|
||||
.join(".agents/plugins/marketplace.json"),
|
||||
r#"{
|
||||
"name": "codex-curated",
|
||||
"plugins": [
|
||||
{
|
||||
"name": "shared-plugin",
|
||||
"source": {
|
||||
"source": "local",
|
||||
"path": "./shared-plugin"
|
||||
}
|
||||
}
|
||||
]
|
||||
}"#,
|
||||
)?;
|
||||
std::fs::create_dir_all(workspace_enabled.path().join(".codex"))?;
|
||||
std::fs::write(
|
||||
workspace_enabled.path().join(".codex/config.toml"),
|
||||
r#"[plugins."shared-plugin@codex-curated"]
|
||||
enabled = false
|
||||
"#,
|
||||
)?;
|
||||
set_project_trust_level(
|
||||
codex_home.path(),
|
||||
workspace_enabled.path(),
|
||||
TrustLevel::Trusted,
|
||||
)?;
|
||||
|
||||
let workspace_default = TempDir::new()?;
|
||||
let home = codex_home.path().to_string_lossy().into_owned();
|
||||
let mut mcp = McpProcess::new_with_env(
|
||||
codex_home.path(),
|
||||
&[
|
||||
("HOME", Some(home.as_str())),
|
||||
("USERPROFILE", Some(home.as_str())),
|
||||
],
|
||||
)
|
||||
.await?;
|
||||
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let request_id = mcp
|
||||
.send_plugin_list_request(PluginListParams {
|
||||
cwds: Some(vec![
|
||||
AbsolutePathBuf::try_from(workspace_enabled.path())?,
|
||||
AbsolutePathBuf::try_from(workspace_default.path())?,
|
||||
]),
|
||||
})
|
||||
.await?;
|
||||
|
||||
let response: JSONRPCResponse = timeout(
|
||||
DEFAULT_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
let response: PluginListResponse = to_response(response)?;
|
||||
|
||||
let shared_plugin = response
|
||||
.marketplaces
|
||||
.iter()
|
||||
.flat_map(|marketplace| marketplace.plugins.iter())
|
||||
.find(|plugin| plugin.name == "shared-plugin")
|
||||
.expect("expected shared-plugin entry");
|
||||
assert_eq!(shared_plugin.enabled, true);
|
||||
Ok(())
|
||||
}
|
||||
@@ -36,7 +36,6 @@ use tempfile::TempDir;
|
||||
use tokio::time::timeout;
|
||||
|
||||
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
const STARTUP_CONTEXT_HEADER: &str = "Startup context from Codex.";
|
||||
|
||||
#[tokio::test]
|
||||
async fn realtime_conversation_streams_v2_notifications() -> Result<()> {
|
||||
@@ -115,18 +114,6 @@ async fn realtime_conversation_streams_v2_notifications() -> Result<()> {
|
||||
assert_eq!(started.thread_id, thread_start.thread.id);
|
||||
assert!(started.session_id.is_some());
|
||||
|
||||
let startup_context_request = realtime_server.wait_for_request(0, 0).await;
|
||||
assert_eq!(
|
||||
startup_context_request.body_json()["type"].as_str(),
|
||||
Some("session.update")
|
||||
);
|
||||
assert!(
|
||||
startup_context_request.body_json()["session"]["instructions"]
|
||||
.as_str()
|
||||
.context("expected startup context instructions")?
|
||||
.contains(STARTUP_CONTEXT_HEADER)
|
||||
);
|
||||
|
||||
let audio_append_request_id = mcp
|
||||
.send_thread_realtime_append_audio_request(ThreadRealtimeAppendAudioParams {
|
||||
thread_id: started.thread_id.clone(),
|
||||
@@ -196,12 +183,6 @@ async fn realtime_conversation_streams_v2_notifications() -> Result<()> {
|
||||
connection[0].body_json()["type"].as_str(),
|
||||
Some("session.update")
|
||||
);
|
||||
assert!(
|
||||
connection[0].body_json()["session"]["instructions"]
|
||||
.as_str()
|
||||
.context("expected startup context instructions")?
|
||||
.contains(STARTUP_CONTEXT_HEADER)
|
||||
);
|
||||
let mut request_types = [
|
||||
connection[1].body_json()["type"]
|
||||
.as_str()
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
use super::connection_handling_websocket::DEFAULT_READ_TIMEOUT;
|
||||
use super::connection_handling_websocket::WsClient;
|
||||
use super::connection_handling_websocket::assert_no_message;
|
||||
use super::connection_handling_websocket::connect_websocket;
|
||||
use super::connection_handling_websocket::create_config_toml;
|
||||
use super::connection_handling_websocket::read_notification_for_method;
|
||||
use super::connection_handling_websocket::read_response_and_notification_for_method;
|
||||
use super::connection_handling_websocket::read_response_for_id;
|
||||
use super::connection_handling_websocket::reserve_local_addr;
|
||||
use super::connection_handling_websocket::send_initialize_request;
|
||||
use super::connection_handling_websocket::send_request;
|
||||
use super::connection_handling_websocket::spawn_websocket_server;
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use app_test_support::create_fake_rollout_with_text_elements;
|
||||
use app_test_support::create_mock_responses_server_repeating_assistant;
|
||||
use app_test_support::to_response;
|
||||
use codex_app_server_protocol::JSONRPCNotification;
|
||||
use codex_app_server_protocol::JSONRPCResponse;
|
||||
use codex_app_server_protocol::ThreadNameUpdatedNotification;
|
||||
use codex_app_server_protocol::ThreadResumeParams;
|
||||
use codex_app_server_protocol::ThreadResumeResponse;
|
||||
use codex_app_server_protocol::ThreadSetNameParams;
|
||||
use codex_app_server_protocol::ThreadSetNameResponse;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tempfile::TempDir;
|
||||
use tokio::time::Duration;
|
||||
use tokio::time::timeout;
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_name_updated_broadcasts_for_loaded_threads() -> Result<()> {
|
||||
let server = create_mock_responses_server_repeating_assistant("Done").await;
|
||||
let codex_home = TempDir::new()?;
|
||||
create_config_toml(codex_home.path(), &server.uri(), "never")?;
|
||||
let conversation_id = create_rollout(codex_home.path(), "2025-01-05T12-00-00")?;
|
||||
|
||||
let bind_addr = reserve_local_addr()?;
|
||||
let mut process = spawn_websocket_server(codex_home.path(), bind_addr).await?;
|
||||
|
||||
let result = async {
|
||||
let mut ws1 = connect_websocket(bind_addr).await?;
|
||||
let mut ws2 = connect_websocket(bind_addr).await?;
|
||||
initialize_both_clients(&mut ws1, &mut ws2).await?;
|
||||
|
||||
send_request(
|
||||
&mut ws1,
|
||||
"thread/resume",
|
||||
10,
|
||||
Some(serde_json::to_value(ThreadResumeParams {
|
||||
thread_id: conversation_id.clone(),
|
||||
..Default::default()
|
||||
})?),
|
||||
)
|
||||
.await?;
|
||||
let resume_resp: JSONRPCResponse = read_response_for_id(&mut ws1, 10).await?;
|
||||
let resume: ThreadResumeResponse = to_response::<ThreadResumeResponse>(resume_resp)?;
|
||||
assert_eq!(resume.thread.id, conversation_id);
|
||||
|
||||
let renamed = "Loaded rename";
|
||||
send_request(
|
||||
&mut ws1,
|
||||
"thread/name/set",
|
||||
11,
|
||||
Some(serde_json::to_value(ThreadSetNameParams {
|
||||
thread_id: conversation_id.clone(),
|
||||
name: renamed.to_string(),
|
||||
})?),
|
||||
)
|
||||
.await?;
|
||||
let (rename_resp, ws1_notification) =
|
||||
read_response_and_notification_for_method(&mut ws1, 11, "thread/name/updated").await?;
|
||||
let _: ThreadSetNameResponse = to_response::<ThreadSetNameResponse>(rename_resp)?;
|
||||
assert_thread_name_updated(ws1_notification, &conversation_id, renamed)?;
|
||||
|
||||
let ws2_notification =
|
||||
read_notification_for_method(&mut ws2, "thread/name/updated").await?;
|
||||
assert_thread_name_updated(ws2_notification, &conversation_id, renamed)?;
|
||||
|
||||
assert_no_message(&mut ws1, Duration::from_millis(250)).await?;
|
||||
assert_no_message(&mut ws2, Duration::from_millis(250)).await?;
|
||||
Ok(())
|
||||
}
|
||||
.await;
|
||||
|
||||
process
|
||||
.kill()
|
||||
.await
|
||||
.context("failed to stop websocket app-server process")?;
|
||||
result
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_name_updated_broadcasts_for_not_loaded_threads() -> Result<()> {
|
||||
let server = create_mock_responses_server_repeating_assistant("Done").await;
|
||||
let codex_home = TempDir::new()?;
|
||||
create_config_toml(codex_home.path(), &server.uri(), "never")?;
|
||||
let conversation_id = create_rollout(codex_home.path(), "2025-01-05T12-05-00")?;
|
||||
|
||||
let bind_addr = reserve_local_addr()?;
|
||||
let mut process = spawn_websocket_server(codex_home.path(), bind_addr).await?;
|
||||
|
||||
let result = async {
|
||||
let mut ws1 = connect_websocket(bind_addr).await?;
|
||||
let mut ws2 = connect_websocket(bind_addr).await?;
|
||||
initialize_both_clients(&mut ws1, &mut ws2).await?;
|
||||
|
||||
let renamed = "Stored rename";
|
||||
send_request(
|
||||
&mut ws1,
|
||||
"thread/name/set",
|
||||
20,
|
||||
Some(serde_json::to_value(ThreadSetNameParams {
|
||||
thread_id: conversation_id.clone(),
|
||||
name: renamed.to_string(),
|
||||
})?),
|
||||
)
|
||||
.await?;
|
||||
let (rename_resp, ws1_notification) =
|
||||
read_response_and_notification_for_method(&mut ws1, 20, "thread/name/updated").await?;
|
||||
let _: ThreadSetNameResponse = to_response::<ThreadSetNameResponse>(rename_resp)?;
|
||||
assert_thread_name_updated(ws1_notification, &conversation_id, renamed)?;
|
||||
|
||||
let ws2_notification =
|
||||
read_notification_for_method(&mut ws2, "thread/name/updated").await?;
|
||||
assert_thread_name_updated(ws2_notification, &conversation_id, renamed)?;
|
||||
|
||||
assert_no_message(&mut ws1, Duration::from_millis(250)).await?;
|
||||
assert_no_message(&mut ws2, Duration::from_millis(250)).await?;
|
||||
Ok(())
|
||||
}
|
||||
.await;
|
||||
|
||||
process
|
||||
.kill()
|
||||
.await
|
||||
.context("failed to stop websocket app-server process")?;
|
||||
result
|
||||
}
|
||||
|
||||
async fn initialize_both_clients(ws1: &mut WsClient, ws2: &mut WsClient) -> Result<()> {
|
||||
send_initialize_request(ws1, 1, "ws_client_one").await?;
|
||||
timeout(DEFAULT_READ_TIMEOUT, read_response_for_id(ws1, 1)).await??;
|
||||
|
||||
send_initialize_request(ws2, 2, "ws_client_two").await?;
|
||||
timeout(DEFAULT_READ_TIMEOUT, read_response_for_id(ws2, 2)).await??;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_rollout(codex_home: &std::path::Path, filename_ts: &str) -> Result<String> {
|
||||
create_fake_rollout_with_text_elements(
|
||||
codex_home,
|
||||
filename_ts,
|
||||
"2025-01-05T12:00:00Z",
|
||||
"Saved user message",
|
||||
Vec::new(),
|
||||
Some("mock_provider"),
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
fn assert_thread_name_updated(
|
||||
notification: JSONRPCNotification,
|
||||
thread_id: &str,
|
||||
thread_name: &str,
|
||||
) -> Result<()> {
|
||||
let notification: ThreadNameUpdatedNotification =
|
||||
serde_json::from_value(notification.params.context("thread/name/updated params")?)?;
|
||||
assert_eq!(notification.thread_id, thread_id);
|
||||
assert_eq!(notification.thread_name.as_deref(), Some(thread_name));
|
||||
Ok(())
|
||||
}
|
||||
@@ -7,10 +7,6 @@ use codex_app_server_protocol::JSONRPCError;
|
||||
use codex_app_server_protocol::JSONRPCResponse;
|
||||
use codex_app_server_protocol::RequestId;
|
||||
use codex_app_server_protocol::SessionSource;
|
||||
use codex_app_server_protocol::ThreadArchiveParams;
|
||||
use codex_app_server_protocol::ThreadArchiveResponse;
|
||||
use codex_app_server_protocol::ThreadForkParams;
|
||||
use codex_app_server_protocol::ThreadForkResponse;
|
||||
use codex_app_server_protocol::ThreadItem;
|
||||
use codex_app_server_protocol::ThreadListParams;
|
||||
use codex_app_server_protocol::ThreadListResponse;
|
||||
@@ -24,8 +20,6 @@ use codex_app_server_protocol::ThreadSetNameResponse;
|
||||
use codex_app_server_protocol::ThreadStartParams;
|
||||
use codex_app_server_protocol::ThreadStartResponse;
|
||||
use codex_app_server_protocol::ThreadStatus;
|
||||
use codex_app_server_protocol::ThreadUnarchiveParams;
|
||||
use codex_app_server_protocol::ThreadUnarchiveResponse;
|
||||
use codex_app_server_protocol::TurnStartParams;
|
||||
use codex_app_server_protocol::TurnStartResponse;
|
||||
use codex_app_server_protocol::TurnStatus;
|
||||
@@ -158,150 +152,6 @@ async fn thread_read_can_include_turns() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_read_include_turns_keeps_fork_history_after_parent_archive_and_unarchive()
|
||||
-> Result<()> {
|
||||
let server = create_mock_responses_server_repeating_assistant("Done").await;
|
||||
let codex_home = TempDir::new()?;
|
||||
create_config_toml(codex_home.path(), &server.uri())?;
|
||||
|
||||
let mut mcp = McpProcess::new(codex_home.path()).await?;
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let start_id = mcp
|
||||
.send_thread_start_request(ThreadStartParams {
|
||||
model: Some("mock-model".to_string()),
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
let start_resp: JSONRPCResponse = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(start_id)),
|
||||
)
|
||||
.await??;
|
||||
let ThreadStartResponse { thread: parent, .. } =
|
||||
to_response::<ThreadStartResponse>(start_resp)?;
|
||||
|
||||
let turn_start_id = mcp
|
||||
.send_turn_start_request(TurnStartParams {
|
||||
thread_id: parent.id.clone(),
|
||||
input: vec![UserInput::Text {
|
||||
text: "parent message".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
let turn_start_resp: JSONRPCResponse = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(turn_start_id)),
|
||||
)
|
||||
.await??;
|
||||
let _: TurnStartResponse = to_response::<TurnStartResponse>(turn_start_resp)?;
|
||||
timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_notification_message("turn/completed"),
|
||||
)
|
||||
.await??;
|
||||
|
||||
let fork_id = mcp
|
||||
.send_thread_fork_request(ThreadForkParams {
|
||||
thread_id: parent.id.clone(),
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
let fork_resp: JSONRPCResponse = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(fork_id)),
|
||||
)
|
||||
.await??;
|
||||
let ThreadForkResponse { thread: child, .. } = to_response::<ThreadForkResponse>(fork_resp)?;
|
||||
|
||||
let read_child_id = mcp
|
||||
.send_thread_read_request(ThreadReadParams {
|
||||
thread_id: child.id.clone(),
|
||||
include_turns: true,
|
||||
})
|
||||
.await?;
|
||||
let read_child_resp: JSONRPCResponse = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(read_child_id)),
|
||||
)
|
||||
.await??;
|
||||
let ThreadReadResponse {
|
||||
thread: child_before_archive,
|
||||
} = to_response::<ThreadReadResponse>(read_child_resp)?;
|
||||
assert_eq!(child_before_archive.turns.len(), 1);
|
||||
|
||||
let archive_id = mcp
|
||||
.send_thread_archive_request(ThreadArchiveParams {
|
||||
thread_id: parent.id.clone(),
|
||||
})
|
||||
.await?;
|
||||
let archive_resp: JSONRPCResponse = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(archive_id)),
|
||||
)
|
||||
.await??;
|
||||
let _: ThreadArchiveResponse = to_response::<ThreadArchiveResponse>(archive_resp)?;
|
||||
timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_notification_message("thread/archived"),
|
||||
)
|
||||
.await??;
|
||||
|
||||
let read_child_id = mcp
|
||||
.send_thread_read_request(ThreadReadParams {
|
||||
thread_id: child.id.clone(),
|
||||
include_turns: true,
|
||||
})
|
||||
.await?;
|
||||
let read_child_resp: JSONRPCResponse = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(read_child_id)),
|
||||
)
|
||||
.await??;
|
||||
let ThreadReadResponse {
|
||||
thread: child_after_archive,
|
||||
} = to_response::<ThreadReadResponse>(read_child_resp)?;
|
||||
assert_eq!(child_after_archive.turns, child_before_archive.turns);
|
||||
|
||||
let unarchive_id = mcp
|
||||
.send_thread_unarchive_request(ThreadUnarchiveParams {
|
||||
thread_id: parent.id,
|
||||
})
|
||||
.await?;
|
||||
let unarchive_resp: JSONRPCResponse = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(unarchive_id)),
|
||||
)
|
||||
.await??;
|
||||
let _: ThreadUnarchiveResponse = to_response::<ThreadUnarchiveResponse>(unarchive_resp)?;
|
||||
timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_notification_message("thread/unarchived"),
|
||||
)
|
||||
.await??;
|
||||
|
||||
let read_child_id = mcp
|
||||
.send_thread_read_request(ThreadReadParams {
|
||||
thread_id: child.id,
|
||||
include_turns: true,
|
||||
})
|
||||
.await?;
|
||||
let read_child_resp: JSONRPCResponse = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(read_child_id)),
|
||||
)
|
||||
.await??;
|
||||
let ThreadReadResponse {
|
||||
thread: child_after_unarchive,
|
||||
} = to_response::<ThreadReadResponse>(read_child_resp)?;
|
||||
assert_eq!(child_after_unarchive.turns, child_before_archive.turns);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_read_loaded_thread_returns_precomputed_path_before_materialization() -> Result<()> {
|
||||
let server = create_mock_responses_server_repeating_assistant("Done").await;
|
||||
@@ -557,62 +407,6 @@ async fn thread_read_include_turns_rejects_unmaterialized_loaded_thread() -> Res
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_read_loaded_ephemeral_thread_ignores_unrelated_rollout_mentions() -> Result<()> {
|
||||
let server = create_mock_responses_server_repeating_assistant("Done").await;
|
||||
let codex_home = TempDir::new()?;
|
||||
create_config_toml(codex_home.path(), &server.uri())?;
|
||||
|
||||
let mut mcp = McpProcess::new(codex_home.path()).await?;
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let start_id = mcp
|
||||
.send_thread_start_request(ThreadStartParams {
|
||||
model: Some("mock-model".to_string()),
|
||||
ephemeral: Some(true),
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
let start_resp: JSONRPCResponse = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(start_id)),
|
||||
)
|
||||
.await??;
|
||||
let ThreadStartResponse { thread, .. } = to_response::<ThreadStartResponse>(start_resp)?;
|
||||
|
||||
let unrelated_preview = thread.id.clone();
|
||||
let _unrelated_rollout_id = create_fake_rollout_with_text_elements(
|
||||
codex_home.path(),
|
||||
"2025-01-05T13-00-00",
|
||||
"2025-01-05T13:00:00Z",
|
||||
&unrelated_preview,
|
||||
vec![],
|
||||
Some("mock_provider"),
|
||||
None,
|
||||
)?;
|
||||
|
||||
let read_id = mcp
|
||||
.send_thread_read_request(ThreadReadParams {
|
||||
thread_id: thread.id.clone(),
|
||||
include_turns: false,
|
||||
})
|
||||
.await?;
|
||||
let read_resp: JSONRPCResponse = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(read_id)),
|
||||
)
|
||||
.await??;
|
||||
let ThreadReadResponse { thread: read } = to_response::<ThreadReadResponse>(read_resp)?;
|
||||
|
||||
assert_eq!(read.id, thread.id);
|
||||
assert!(read.ephemeral);
|
||||
assert_eq!(read.path, None);
|
||||
assert!(read.preview.is_empty());
|
||||
assert_eq!(read.status, ThreadStatus::Idle);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_read_reports_system_error_idle_flag_after_failed_turn() -> Result<()> {
|
||||
let server = responses::start_mock_server().await;
|
||||
|
||||
@@ -53,14 +53,9 @@ use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use tempfile::TempDir;
|
||||
use tokio::time::sleep;
|
||||
use tokio::time::timeout;
|
||||
use uuid::Uuid;
|
||||
use wiremock::MockServer;
|
||||
|
||||
#[cfg(windows)]
|
||||
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(25);
|
||||
#[cfg(not(windows))]
|
||||
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
|
||||
const CODEX_5_2_INSTRUCTIONS_TEMPLATE_DEFAULT: &str = "You are Codex, a coding agent based on GPT-5. You and the user share the same workspace and collaborate to achieve the user's goals.";
|
||||
|
||||
@@ -863,7 +858,16 @@ async fn thread_resume_rejoins_running_thread_even_with_override_mismatch() -> R
|
||||
async fn thread_resume_replays_pending_command_execution_request_approval() -> Result<()> {
|
||||
let responses = vec![
|
||||
create_final_assistant_message_sse_response("seeded")?,
|
||||
create_shell_command_sse_response(fast_shell_command(), None, Some(1000), "call-1")?,
|
||||
create_shell_command_sse_response(
|
||||
vec![
|
||||
"python3".to_string(),
|
||||
"-c".to_string(),
|
||||
"print(42)".to_string(),
|
||||
],
|
||||
None,
|
||||
Some(5000),
|
||||
"call-1",
|
||||
)?,
|
||||
create_final_assistant_message_sse_response("done")?,
|
||||
];
|
||||
let server = create_mock_responses_server_sequence(responses).await;
|
||||
@@ -981,7 +985,6 @@ async fn thread_resume_replays_pending_command_execution_request_approval() -> R
|
||||
primary.read_stream_until_notification_message("turn/completed"),
|
||||
)
|
||||
.await??;
|
||||
wait_for_mock_request_count(&server, 3).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1147,50 +1150,10 @@ async fn thread_resume_replays_pending_file_change_request_approval() -> Result<
|
||||
primary.read_stream_until_notification_message("turn/completed"),
|
||||
)
|
||||
.await??;
|
||||
wait_for_mock_request_count(&server, 3).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fast_shell_command() -> Vec<String> {
|
||||
if cfg!(windows) {
|
||||
vec![
|
||||
"cmd".to_string(),
|
||||
"/d".to_string(),
|
||||
"/c".to_string(),
|
||||
"echo 42".to_string(),
|
||||
]
|
||||
} else {
|
||||
vec![
|
||||
"python3".to_string(),
|
||||
"-c".to_string(),
|
||||
"print(42)".to_string(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async fn wait_for_mock_request_count(server: &MockServer, expected: usize) -> Result<()> {
|
||||
let deadline = tokio::time::Instant::now() + DEFAULT_READ_TIMEOUT;
|
||||
loop {
|
||||
let requests = server
|
||||
.received_requests()
|
||||
.await
|
||||
.ok_or_else(|| anyhow::anyhow!("failed to fetch received requests"))?;
|
||||
if requests.len() >= expected {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if tokio::time::Instant::now() >= deadline {
|
||||
anyhow::bail!(
|
||||
"expected at least {expected} mock requests, observed {}",
|
||||
requests.len()
|
||||
);
|
||||
}
|
||||
|
||||
sleep(std::time::Duration::from_millis(50)).await;
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_resume_with_overrides_defers_updated_at_until_turn_start() -> Result<()> {
|
||||
let server = create_mock_responses_server_repeating_assistant("Done").await;
|
||||
|
||||
@@ -32,9 +32,6 @@ use pretty_assertions::assert_eq;
|
||||
use tempfile::TempDir;
|
||||
use tokio::time::timeout;
|
||||
|
||||
#[cfg(windows)]
|
||||
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(25);
|
||||
#[cfg(not(windows))]
|
||||
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -1016,9 +1016,27 @@ async fn turn_start_exec_approval_toggle_v2() -> Result<()> {
|
||||
// Mock server: first turn requests a shell call (elicitation), then completes.
|
||||
// Second turn same, but we'll set approval_policy=never to avoid elicitation.
|
||||
let responses = vec![
|
||||
create_shell_command_sse_response(fast_shell_command(), None, Some(1000), "call1")?,
|
||||
create_shell_command_sse_response(
|
||||
vec![
|
||||
"python3".to_string(),
|
||||
"-c".to_string(),
|
||||
"print(42)".to_string(),
|
||||
],
|
||||
None,
|
||||
Some(5000),
|
||||
"call1",
|
||||
)?,
|
||||
create_final_assistant_message_sse_response("done 1")?,
|
||||
create_shell_command_sse_response(fast_shell_command(), None, Some(1000), "call2")?,
|
||||
create_shell_command_sse_response(
|
||||
vec![
|
||||
"python3".to_string(),
|
||||
"-c".to_string(),
|
||||
"print(42)".to_string(),
|
||||
],
|
||||
None,
|
||||
Some(5000),
|
||||
"call2",
|
||||
)?,
|
||||
create_final_assistant_message_sse_response("done 2")?,
|
||||
];
|
||||
let server = create_mock_responses_server_sequence(responses).await;
|
||||
@@ -1148,23 +1166,6 @@ async fn turn_start_exec_approval_toggle_v2() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fast_shell_command() -> Vec<String> {
|
||||
if cfg!(windows) {
|
||||
vec![
|
||||
"cmd".to_string(),
|
||||
"/d".to_string(),
|
||||
"/c".to_string(),
|
||||
"echo 42".to_string(),
|
||||
]
|
||||
} else {
|
||||
vec![
|
||||
"python3".to_string(),
|
||||
"-c".to_string(),
|
||||
"print(42)".to_string(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn turn_start_exec_approval_decline_v2() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user