mirror of
https://github.com/openai/codex.git
synced 2026-03-06 06:33:21 +00:00
Compare commits
7 Commits
latest-alp
...
pr13449
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6790e7fa4a | ||
|
|
ed7a864c46 | ||
|
|
1434bba73c | ||
|
|
fab10a6b54 | ||
|
|
2adc12ed7c | ||
|
|
6c47eda8a4 | ||
|
|
7abd70178a |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -47,7 +47,7 @@ jobs:
|
||||
echo "pack_output=$PACK_OUTPUT" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Upload staged npm package artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: codex-npm-staging
|
||||
path: ${{ steps.stage_npm_package.outputs.pack_output }}
|
||||
|
||||
4
.github/workflows/rust-ci.yml
vendored
4
.github/workflows/rust-ci.yml
vendored
@@ -392,7 +392,7 @@ jobs:
|
||||
|
||||
- name: Upload Cargo timings (clippy)
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: cargo-timings-rust-ci-clippy-${{ matrix.target }}-${{ matrix.profile }}
|
||||
path: codex-rs/target/**/cargo-timings/cargo-timing.html
|
||||
@@ -605,7 +605,7 @@ jobs:
|
||||
|
||||
- name: Upload Cargo timings (nextest)
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: cargo-timings-rust-ci-nextest-${{ matrix.target }}-${{ matrix.profile }}
|
||||
path: codex-rs/target/**/cargo-timings/cargo-timing.html
|
||||
|
||||
10
.github/workflows/rust-release-windows.yml
vendored
10
.github/workflows/rust-release-windows.yml
vendored
@@ -92,7 +92,7 @@ jobs:
|
||||
cargo build --target ${{ matrix.target }} --release --timings ${{ matrix.build_args }}
|
||||
|
||||
- name: Upload Cargo timings
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: cargo-timings-rust-release-windows-${{ matrix.target }}-${{ matrix.bundle }}
|
||||
path: codex-rs/target/**/cargo-timings/cargo-timing.html
|
||||
@@ -112,7 +112,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Upload Windows binaries
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: windows-binaries-${{ matrix.target }}-${{ matrix.bundle }}
|
||||
path: |
|
||||
@@ -150,13 +150,13 @@ jobs:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Download prebuilt Windows primary binaries
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: windows-binaries-${{ matrix.target }}-primary
|
||||
path: codex-rs/target/${{ matrix.target }}/release
|
||||
|
||||
- name: Download prebuilt Windows helper binaries
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: windows-binaries-${{ matrix.target }}-helpers
|
||||
path: codex-rs/target/${{ matrix.target }}/release
|
||||
@@ -257,7 +257,7 @@ jobs:
|
||||
"${GITHUB_WORKSPACE}/.github/workflows/zstd" -T0 -19 "$dest/$base"
|
||||
done
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
- uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: |
|
||||
|
||||
11
.github/workflows/rust-release.yml
vendored
11
.github/workflows/rust-release.yml
vendored
@@ -57,9 +57,7 @@ jobs:
|
||||
run:
|
||||
working-directory: codex-rs
|
||||
env:
|
||||
# 2026-03-04: temporarily change releases to use thin LTO because
|
||||
# Ubuntu ARM is timing out at 60 minutes.
|
||||
CARGO_PROFILE_RELEASE_LTO: ${{ contains(github.ref_name, '-alpha') && 'thin' || 'thin' }}
|
||||
CARGO_PROFILE_RELEASE_LTO: ${{ contains(github.ref_name, '-alpha') && 'thin' || 'fat' }}
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -213,11 +211,10 @@ jobs:
|
||||
- name: Cargo build
|
||||
shell: bash
|
||||
run: |
|
||||
echo "CARGO_PROFILE_RELEASE_LTO: ${CARGO_PROFILE_RELEASE_LTO}"
|
||||
cargo build --target ${{ matrix.target }} --release --timings --bin codex --bin codex-responses-api-proxy
|
||||
|
||||
- name: Upload Cargo timings
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: cargo-timings-rust-release-${{ matrix.target }}
|
||||
path: codex-rs/target/**/cargo-timings/cargo-timing.html
|
||||
@@ -356,7 +353,7 @@ jobs:
|
||||
zstd -T0 -19 --rm "$dest/$base"
|
||||
done
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
- uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
# Upload the per-binary .zst files as well as the new .tar.gz
|
||||
@@ -420,7 +417,7 @@ jobs:
|
||||
|
||||
echo "path=${notes_path}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
- uses: actions/download-artifact@v8
|
||||
- uses: actions/download-artifact@v7
|
||||
with:
|
||||
path: dist
|
||||
|
||||
|
||||
14
.github/workflows/shell-tool-mcp.yml
vendored
14
.github/workflows/shell-tool-mcp.yml
vendored
@@ -158,7 +158,7 @@ jobs:
|
||||
mkdir -p "$dest"
|
||||
cp bash "$dest/bash"
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
- uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: shell-tool-mcp-bash-${{ matrix.target }}-${{ matrix.variant }}
|
||||
path: artifacts/**
|
||||
@@ -199,7 +199,7 @@ jobs:
|
||||
mkdir -p "$dest"
|
||||
cp bash "$dest/bash"
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
- uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: shell-tool-mcp-bash-${{ matrix.target }}-${{ matrix.variant }}
|
||||
path: artifacts/**
|
||||
@@ -325,7 +325,7 @@ jobs:
|
||||
grep -Fx "smoke-zsh" "$tmpdir/stdout.txt"
|
||||
grep -Fx "/bin/echo" "$tmpdir/wrapper.log"
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
- uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: shell-tool-mcp-zsh-${{ matrix.target }}-${{ matrix.variant }}
|
||||
path: artifacts/**
|
||||
@@ -403,7 +403,7 @@ jobs:
|
||||
grep -Fx "smoke-zsh" "$tmpdir/stdout.txt"
|
||||
grep -Fx "/bin/echo" "$tmpdir/wrapper.log"
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
- uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: shell-tool-mcp-zsh-${{ matrix.target }}-${{ matrix.variant }}
|
||||
path: artifacts/**
|
||||
@@ -441,7 +441,7 @@ jobs:
|
||||
run: pnpm --filter @openai/codex-shell-tool-mcp run build
|
||||
|
||||
- name: Download build artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
@@ -500,7 +500,7 @@ jobs:
|
||||
filename=$(PACK_INFO="$pack_info" node -e 'const data = JSON.parse(process.env.PACK_INFO); console.log(data[0].filename);')
|
||||
mv "dist/npm/${filename}" "dist/npm/codex-shell-tool-mcp-npm-${PACKAGE_VERSION}.tgz"
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
- uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: codex-shell-tool-mcp-npm
|
||||
path: dist/npm/codex-shell-tool-mcp-npm-${{ env.PACKAGE_VERSION }}.tgz
|
||||
@@ -529,7 +529,7 @@ jobs:
|
||||
run: npm install -g npm@latest
|
||||
|
||||
- name: Download npm tarball
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: codex-shell-tool-mcp-npm
|
||||
path: dist/npm
|
||||
|
||||
50
MODULE.bazel.lock
generated
50
MODULE.bazel.lock
generated
File diff suppressed because one or more lines are too long
728
codex-rs/Cargo.lock
generated
728
codex-rs/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -33,6 +33,8 @@ members = [
|
||||
"mcp-server",
|
||||
"network-proxy",
|
||||
"ollama",
|
||||
"artifact-presentation",
|
||||
"artifact-spreadsheet",
|
||||
"process-hardening",
|
||||
"protocol",
|
||||
"rmcp-client",
|
||||
@@ -64,13 +66,11 @@ members = [
|
||||
"state",
|
||||
"codex-experimental-api-macros",
|
||||
"test-macros",
|
||||
"package-manager",
|
||||
"artifacts",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
version = "0.112.0-alpha.2"
|
||||
version = "0.0.0"
|
||||
# Track the edition for all workspace crates in one place. Individual
|
||||
# crates can still override this value, but keeping it here means new
|
||||
# crates created with `cargo new -w ...` automatically inherit the 2024
|
||||
@@ -83,8 +83,6 @@ license = "Apache-2.0"
|
||||
app_test_support = { path = "app-server/tests/common" }
|
||||
codex-ansi-escape = { path = "ansi-escape" }
|
||||
codex-api = { path = "codex-api" }
|
||||
codex-artifacts = { path = "artifacts" }
|
||||
codex-package-manager = { path = "package-manager" }
|
||||
codex-app-server = { path = "app-server" }
|
||||
codex-app-server-protocol = { path = "app-server-protocol" }
|
||||
codex-app-server-test-client = { path = "app-server-test-client" }
|
||||
@@ -113,6 +111,8 @@ codex-mcp-server = { path = "mcp-server" }
|
||||
codex-network-proxy = { path = "network-proxy" }
|
||||
codex-ollama = { path = "ollama" }
|
||||
codex-otel = { path = "otel" }
|
||||
codex-artifact-presentation = { path = "artifact-presentation" }
|
||||
codex-artifact-spreadsheet = { path = "artifact-spreadsheet" }
|
||||
codex-process-hardening = { path = "process-hardening" }
|
||||
codex-protocol = { path = "protocol" }
|
||||
codex-responses-api-proxy = { path = "responses-api-proxy" }
|
||||
@@ -178,11 +178,9 @@ 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"
|
||||
flate2 = "1.1.4"
|
||||
futures = { version = "0.3", default-features = false }
|
||||
gethostname = "1.1.0"
|
||||
globset = "0.4"
|
||||
@@ -221,6 +219,7 @@ owo-colors = "4.3.0"
|
||||
path-absolutize = "3.1.1"
|
||||
pathdiff = "0.2"
|
||||
portable-pty = "0.9.0"
|
||||
ppt-rs = "0.2.6"
|
||||
predicates = "3"
|
||||
pretty_assertions = "1.4.1"
|
||||
pulldown-cmark = "0.10"
|
||||
@@ -243,7 +242,7 @@ sentry = "0.46.0"
|
||||
serde = "1"
|
||||
serde_json = "1"
|
||||
serde_path_to_error = "0.1.20"
|
||||
serde_with = "3.17"
|
||||
serde_with = "3.16"
|
||||
serde_yaml = "0.9"
|
||||
serial_test = "3.2.0"
|
||||
sha1 = "0.10.6"
|
||||
@@ -263,12 +262,11 @@ sqlx = { version = "0.8.6", default-features = false, features = [
|
||||
] }
|
||||
starlark = "0.13.0"
|
||||
strum = "0.27.2"
|
||||
strum_macros = "0.28.0"
|
||||
strum_macros = "0.27.2"
|
||||
supports-color = "3.0.2"
|
||||
syntect = "5"
|
||||
sys-locale = "0.3.2"
|
||||
tempfile = "3.23.0"
|
||||
tar = "0.4.44"
|
||||
test-log = "0.2.19"
|
||||
textwrap = "0.16.2"
|
||||
thiserror = "2.0.17"
|
||||
@@ -355,7 +353,8 @@ ignored = [
|
||||
"icu_provider",
|
||||
"openssl-sys",
|
||||
"codex-utils-readiness",
|
||||
"codex-secrets"
|
||||
"codex-secrets",
|
||||
"codex-artifact-spreadsheet"
|
||||
]
|
||||
|
||||
[profile.release]
|
||||
|
||||
@@ -88,7 +88,6 @@ codex --sandbox danger-full-access
|
||||
```
|
||||
|
||||
The same setting can be persisted in `~/.codex/config.toml` via the top-level `sandbox_mode = "MODE"` key, e.g. `sandbox_mode = "workspace-write"`.
|
||||
In `workspace-write`, Codex also includes `~/.codex/memories` in its writable roots so memory maintenance does not require an extra approval.
|
||||
|
||||
## Code Organization
|
||||
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -951,27 +951,6 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginInstallParams": {
|
||||
"properties": {
|
||||
"cwd": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"marketplaceName": {
|
||||
"type": "string"
|
||||
},
|
||||
"pluginName": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"marketplaceName",
|
||||
"pluginName"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ProductSurface": {
|
||||
"enum": [
|
||||
"chatgpt",
|
||||
@@ -1437,40 +1416,6 @@
|
||||
"title": "WebSearchCallResponseItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"result": {
|
||||
"type": "string"
|
||||
},
|
||||
"revised_prompt": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"image_generation_call"
|
||||
],
|
||||
"title": "ImageGenerationCallResponseItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"result",
|
||||
"status",
|
||||
"type"
|
||||
],
|
||||
"title": "ImageGenerationCallResponseItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"ghost_commit": {
|
||||
@@ -1783,8 +1728,7 @@
|
||||
},
|
||||
"ServiceTier": {
|
||||
"enum": [
|
||||
"fast",
|
||||
"flex"
|
||||
"fast"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
@@ -2885,12 +2829,6 @@
|
||||
},
|
||||
"WindowsSandboxSetupStartParams": {
|
||||
"properties": {
|
||||
"cwd": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"mode": {
|
||||
"$ref": "#/definitions/WindowsSandboxSetupMode"
|
||||
}
|
||||
@@ -3360,30 +3298,6 @@
|
||||
"title": "Skills/config/writeRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"plugin/install"
|
||||
],
|
||||
"title": "Plugin/installRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/PluginInstallParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Plugin/installRequest",
|
||||
"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": {
|
||||
|
||||
@@ -544,56 +544,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"ElicitationRequest": {
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"mode": {
|
||||
"enum": [
|
||||
"form"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"requested_schema": true
|
||||
},
|
||||
"required": [
|
||||
"message",
|
||||
"mode",
|
||||
"requested_schema"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"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": [
|
||||
@@ -1437,60 +1387,6 @@
|
||||
"title": "WebSearchEndEventMsg",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"image_generation_begin"
|
||||
],
|
||||
"title": "ImageGenerationBeginEventMsgType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"call_id",
|
||||
"type"
|
||||
],
|
||||
"title": "ImageGenerationBeginEventMsg",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"result": {
|
||||
"type": "string"
|
||||
},
|
||||
"revised_prompt": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"image_generation_end"
|
||||
],
|
||||
"title": "ImageGenerationEndEventMsgType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"call_id",
|
||||
"result",
|
||||
"status",
|
||||
"type"
|
||||
],
|
||||
"title": "ImageGenerationEndEventMsg",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "Notification that the server is about to execute a command.",
|
||||
"properties": {
|
||||
@@ -2013,8 +1909,8 @@
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"request": {
|
||||
"$ref": "#/definitions/ElicitationRequest"
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"server_name": {
|
||||
"type": "string"
|
||||
@@ -2029,7 +1925,7 @@
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"request",
|
||||
"message",
|
||||
"server_name",
|
||||
"type"
|
||||
],
|
||||
@@ -3756,64 +3652,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": {
|
||||
"type": "boolean"
|
||||
"accessibility": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"macos_automation": {
|
||||
"$ref": "#/definitions/MacOsAutomationPermission"
|
||||
"automations": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MacOsAutomationValue"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"macos_calendar": {
|
||||
"type": "boolean"
|
||||
"calendar": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"macos_preferences": {
|
||||
"$ref": "#/definitions/MacOsPreferencesPermission"
|
||||
"preferences": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MacOsPreferencesValue"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"macos_accessibility",
|
||||
"macos_automation",
|
||||
"macos_calendar",
|
||||
"macos_preferences"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"MacOsPreferencesValue": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"McpAuthStatus": {
|
||||
"enum": [
|
||||
"unsupported",
|
||||
@@ -4157,7 +4055,7 @@
|
||||
"macos": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MacOsSeatbeltProfileExtensions"
|
||||
"$ref": "#/definitions/MacOsPermissions"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
@@ -5085,40 +4983,6 @@
|
||||
"title": "WebSearchCallResponseItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"result": {
|
||||
"type": "string"
|
||||
},
|
||||
"revised_prompt": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"image_generation_call"
|
||||
],
|
||||
"title": "ImageGenerationCallResponseItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"result",
|
||||
"status",
|
||||
"type"
|
||||
],
|
||||
"title": "ImageGenerationCallResponseItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"ghost_commit": {
|
||||
@@ -5596,8 +5460,7 @@
|
||||
},
|
||||
"ServiceTier": {
|
||||
"enum": [
|
||||
"fast",
|
||||
"flex"
|
||||
"fast"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
@@ -6103,40 +5966,6 @@
|
||||
"title": "WebSearchTurnItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"result": {
|
||||
"type": "string"
|
||||
},
|
||||
"revised_prompt": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"ImageGeneration"
|
||||
],
|
||||
"title": "ImageGenerationTurnItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"result",
|
||||
"status",
|
||||
"type"
|
||||
],
|
||||
"title": "ImageGenerationTurnItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -7228,60 +7057,6 @@
|
||||
"title": "WebSearchEndEventMsg",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"image_generation_begin"
|
||||
],
|
||||
"title": "ImageGenerationBeginEventMsgType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"call_id",
|
||||
"type"
|
||||
],
|
||||
"title": "ImageGenerationBeginEventMsg",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"result": {
|
||||
"type": "string"
|
||||
},
|
||||
"revised_prompt": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"image_generation_end"
|
||||
],
|
||||
"title": "ImageGenerationEndEventMsgType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"call_id",
|
||||
"result",
|
||||
"status",
|
||||
"type"
|
||||
],
|
||||
"title": "ImageGenerationEndEventMsg",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "Notification that the server is about to execute a command.",
|
||||
"properties": {
|
||||
@@ -7804,8 +7579,8 @@
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"request": {
|
||||
"$ref": "#/definitions/ElicitationRequest"
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"server_name": {
|
||||
"type": "string"
|
||||
@@ -7820,7 +7595,7 @@
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"request",
|
||||
"message",
|
||||
"server_name",
|
||||
"type"
|
||||
],
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"mode": {
|
||||
"enum": [
|
||||
"form"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"requestedSchema": true
|
||||
},
|
||||
"required": [
|
||||
"message",
|
||||
"mode",
|
||||
"requestedSchema"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"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,26 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"McpServerElicitationAction": {
|
||||
"enum": [
|
||||
"accept",
|
||||
"decline",
|
||||
"cancel"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"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": [
|
||||
@@ -2258,40 +2251,6 @@
|
||||
"title": "ImageViewThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"result": {
|
||||
"type": "string"
|
||||
},
|
||||
"revisedPrompt": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"imageGeneration"
|
||||
],
|
||||
"title": "ImageGenerationThreadItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"result",
|
||||
"status",
|
||||
"type"
|
||||
],
|
||||
"title": "ImageGenerationThreadItem",
|
||||
"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": {
|
||||
@@ -615,110 +629,28 @@
|
||||
],
|
||||
"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"
|
||||
},
|
||||
"McpServerElicitationRequestParams": {
|
||||
"oneOf": [
|
||||
"MacOsPreferencesValue": {
|
||||
"anyOf": [
|
||||
{
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"mode": {
|
||||
"enum": [
|
||||
"form"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"requestedSchema": true
|
||||
},
|
||||
"required": [
|
||||
"message",
|
||||
"mode",
|
||||
"requestedSchema"
|
||||
],
|
||||
"type": "object"
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"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": {
|
||||
@@ -1049,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": {
|
||||
|
||||
@@ -27,24 +27,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": {
|
||||
@@ -812,30 +826,6 @@
|
||||
"title": "Skills/config/writeRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/v2/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"plugin/install"
|
||||
],
|
||||
"title": "Plugin/installRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/v2/PluginInstallParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Plugin/installRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -1760,56 +1750,6 @@
|
||||
"title": "DynamicToolCallResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"ElicitationRequest": {
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"mode": {
|
||||
"enum": [
|
||||
"form"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"requested_schema": true
|
||||
},
|
||||
"required": [
|
||||
"message",
|
||||
"mode",
|
||||
"requested_schema"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"elicitation_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"mode": {
|
||||
"enum": [
|
||||
"url"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"elicitation_id",
|
||||
"message",
|
||||
"mode",
|
||||
"url"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"EventMsg": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"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.",
|
||||
@@ -2654,60 +2594,6 @@
|
||||
"title": "WebSearchEndEventMsg",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"image_generation_begin"
|
||||
],
|
||||
"title": "ImageGenerationBeginEventMsgType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"call_id",
|
||||
"type"
|
||||
],
|
||||
"title": "ImageGenerationBeginEventMsg",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"result": {
|
||||
"type": "string"
|
||||
},
|
||||
"revised_prompt": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"image_generation_end"
|
||||
],
|
||||
"title": "ImageGenerationEndEventMsgType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"call_id",
|
||||
"result",
|
||||
"status",
|
||||
"type"
|
||||
],
|
||||
"title": "ImageGenerationEndEventMsg",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "Notification that the server is about to execute a command.",
|
||||
"properties": {
|
||||
@@ -3230,8 +3116,8 @@
|
||||
"id": {
|
||||
"$ref": "#/definitions/v2/RequestId"
|
||||
},
|
||||
"request": {
|
||||
"$ref": "#/definitions/ElicitationRequest"
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"server_name": {
|
||||
"type": "string"
|
||||
@@ -3246,7 +3132,7 @@
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"request",
|
||||
"message",
|
||||
"server_name",
|
||||
"type"
|
||||
],
|
||||
@@ -5215,64 +5101,66 @@
|
||||
"title": "JSONRPCResponse",
|
||||
"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": {
|
||||
"type": "boolean"
|
||||
"accessibility": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"macos_automation": {
|
||||
"$ref": "#/definitions/MacOsAutomationPermission"
|
||||
"automations": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MacOsAutomationValue"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"macos_calendar": {
|
||||
"type": "boolean"
|
||||
"calendar": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"macos_preferences": {
|
||||
"$ref": "#/definitions/MacOsPreferencesPermission"
|
||||
"preferences": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MacOsPreferencesValue"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"macos_accessibility",
|
||||
"macos_automation",
|
||||
"macos_calendar",
|
||||
"macos_preferences"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"MacOsPreferencesValue": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"McpInvocation": {
|
||||
"properties": {
|
||||
"arguments": {
|
||||
@@ -5293,102 +5181,6 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpServerElicitationAction": {
|
||||
"enum": [
|
||||
"accept",
|
||||
"decline",
|
||||
"cancel"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"McpServerElicitationRequestParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"mode": {
|
||||
"enum": [
|
||||
"form"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"requestedSchema": true
|
||||
},
|
||||
"required": [
|
||||
"message",
|
||||
"mode",
|
||||
"requestedSchema"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"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"
|
||||
},
|
||||
"McpServerElicitationRequestResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"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"
|
||||
},
|
||||
"McpStartupFailure": {
|
||||
"properties": {
|
||||
"error": {
|
||||
@@ -5654,7 +5446,7 @@
|
||||
"macos": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MacOsSeatbeltProfileExtensions"
|
||||
"$ref": "#/definitions/MacOsPermissions"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
@@ -7102,31 +6894,6 @@
|
||||
"title": "Item/tool/requestUserInputRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "Request input for an MCP server elicitation.",
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/v2/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": {
|
||||
@@ -7586,40 +7353,6 @@
|
||||
"title": "WebSearchTurnItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"result": {
|
||||
"type": "string"
|
||||
},
|
||||
"revised_prompt": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"ImageGeneration"
|
||||
],
|
||||
"title": "ImageGenerationTurnItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"result",
|
||||
"status",
|
||||
"type"
|
||||
],
|
||||
"title": "ImageGenerationTurnItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -7975,13 +7708,6 @@
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"pluginDisplayNames": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -11171,34 +10897,6 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginInstallParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"cwd": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"marketplaceName": {
|
||||
"type": "string"
|
||||
},
|
||||
"pluginName": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"marketplaceName",
|
||||
"pluginName"
|
||||
],
|
||||
"title": "PluginInstallParams",
|
||||
"type": "object"
|
||||
},
|
||||
"PluginInstallResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "PluginInstallResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"ProductSurface": {
|
||||
"enum": [
|
||||
"chatgpt",
|
||||
@@ -12063,40 +11761,6 @@
|
||||
"title": "WebSearchCallResponseItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"result": {
|
||||
"type": "string"
|
||||
},
|
||||
"revised_prompt": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"image_generation_call"
|
||||
],
|
||||
"title": "ImageGenerationCallResponseItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"result",
|
||||
"status",
|
||||
"type"
|
||||
],
|
||||
"title": "ImageGenerationCallResponseItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"ghost_commit": {
|
||||
@@ -12470,8 +12134,7 @@
|
||||
},
|
||||
"ServiceTier": {
|
||||
"enum": [
|
||||
"fast",
|
||||
"flex"
|
||||
"fast"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
@@ -13845,40 +13508,6 @@
|
||||
"title": "ImageViewThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"result": {
|
||||
"type": "string"
|
||||
},
|
||||
"revisedPrompt": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"imageGeneration"
|
||||
],
|
||||
"title": "ImageGenerationThreadItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"result",
|
||||
"status",
|
||||
"type"
|
||||
],
|
||||
"title": "ImageGenerationThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -15661,12 +15290,6 @@
|
||||
"WindowsSandboxSetupStartParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"cwd": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"mode": {
|
||||
"$ref": "#/definitions/v2/WindowsSandboxSetupMode"
|
||||
}
|
||||
|
||||
@@ -404,13 +404,6 @@
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"pluginDisplayNames": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1307,30 +1300,6 @@
|
||||
"title": "Skills/config/writeRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"plugin/install"
|
||||
],
|
||||
"title": "Plugin/installRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/PluginInstallParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Plugin/installRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -3293,56 +3262,6 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ElicitationRequest": {
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"mode": {
|
||||
"enum": [
|
||||
"form"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"requested_schema": true
|
||||
},
|
||||
"required": [
|
||||
"message",
|
||||
"mode",
|
||||
"requested_schema"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"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": {
|
||||
@@ -4212,60 +4131,6 @@
|
||||
"title": "WebSearchEndEventMsg",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"image_generation_begin"
|
||||
],
|
||||
"title": "ImageGenerationBeginEventMsgType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"call_id",
|
||||
"type"
|
||||
],
|
||||
"title": "ImageGenerationBeginEventMsg",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"call_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"result": {
|
||||
"type": "string"
|
||||
},
|
||||
"revised_prompt": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"image_generation_end"
|
||||
],
|
||||
"title": "ImageGenerationEndEventMsgType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"call_id",
|
||||
"result",
|
||||
"status",
|
||||
"type"
|
||||
],
|
||||
"title": "ImageGenerationEndEventMsg",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "Notification that the server is about to execute a command.",
|
||||
"properties": {
|
||||
@@ -4788,8 +4653,8 @@
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"request": {
|
||||
"$ref": "#/definitions/ElicitationRequest"
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"server_name": {
|
||||
"type": "string"
|
||||
@@ -4804,7 +4669,7 @@
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"request",
|
||||
"message",
|
||||
"server_name",
|
||||
"type"
|
||||
],
|
||||
@@ -7350,64 +7215,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": {
|
||||
"type": "boolean"
|
||||
"accessibility": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"macos_automation": {
|
||||
"$ref": "#/definitions/MacOsAutomationPermission"
|
||||
"automations": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MacOsAutomationValue"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"macos_calendar": {
|
||||
"type": "boolean"
|
||||
"calendar": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"macos_preferences": {
|
||||
"$ref": "#/definitions/MacOsPreferencesPermission"
|
||||
"preferences": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MacOsPreferencesValue"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"macos_accessibility",
|
||||
"macos_automation",
|
||||
"macos_calendar",
|
||||
"macos_preferences"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"MacOsPreferencesValue": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"McpAuthStatus": {
|
||||
"enum": [
|
||||
"unsupported",
|
||||
@@ -8284,7 +8151,7 @@
|
||||
"macos": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/MacOsSeatbeltProfileExtensions"
|
||||
"$ref": "#/definitions/MacOsPermissions"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
@@ -8368,34 +8235,6 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginInstallParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"cwd": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"marketplaceName": {
|
||||
"type": "string"
|
||||
},
|
||||
"pluginName": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"marketplaceName",
|
||||
"pluginName"
|
||||
],
|
||||
"title": "PluginInstallParams",
|
||||
"type": "object"
|
||||
},
|
||||
"PluginInstallResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "PluginInstallResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"ProductSurface": {
|
||||
"enum": [
|
||||
"chatgpt",
|
||||
@@ -9485,40 +9324,6 @@
|
||||
"title": "WebSearchCallResponseItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"result": {
|
||||
"type": "string"
|
||||
},
|
||||
"revised_prompt": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"image_generation_call"
|
||||
],
|
||||
"title": "ImageGenerationCallResponseItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"result",
|
||||
"status",
|
||||
"type"
|
||||
],
|
||||
"title": "ImageGenerationCallResponseItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"ghost_commit": {
|
||||
@@ -10965,8 +10770,7 @@
|
||||
},
|
||||
"ServiceTier": {
|
||||
"enum": [
|
||||
"fast",
|
||||
"flex"
|
||||
"fast"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
@@ -12367,40 +12171,6 @@
|
||||
"title": "ImageViewThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"result": {
|
||||
"type": "string"
|
||||
},
|
||||
"revisedPrompt": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"imageGeneration"
|
||||
],
|
||||
"title": "ImageGenerationThreadItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"result",
|
||||
"status",
|
||||
"type"
|
||||
],
|
||||
"title": "ImageGenerationThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -13888,40 +13658,6 @@
|
||||
"title": "WebSearchTurnItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"result": {
|
||||
"type": "string"
|
||||
},
|
||||
"revised_prompt": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"ImageGeneration"
|
||||
],
|
||||
"title": "ImageGenerationTurnItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"result",
|
||||
"status",
|
||||
"type"
|
||||
],
|
||||
"title": "ImageGenerationTurnItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -14455,12 +14191,6 @@
|
||||
"WindowsSandboxSetupStartParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"cwd": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"mode": {
|
||||
"$ref": "#/definitions/WindowsSandboxSetupMode"
|
||||
}
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -707,8 +707,7 @@
|
||||
},
|
||||
"ServiceTier": {
|
||||
"enum": [
|
||||
"fast",
|
||||
"flex"
|
||||
"fast"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -863,40 +863,6 @@
|
||||
"title": "ImageViewThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"result": {
|
||||
"type": "string"
|
||||
},
|
||||
"revisedPrompt": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"imageGeneration"
|
||||
],
|
||||
"title": "ImageGenerationThreadItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"result",
|
||||
"status",
|
||||
"type"
|
||||
],
|
||||
"title": "ImageGenerationThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
|
||||
@@ -863,40 +863,6 @@
|
||||
"title": "ImageViewThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"result": {
|
||||
"type": "string"
|
||||
},
|
||||
"revisedPrompt": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"imageGeneration"
|
||||
],
|
||||
"title": "ImageGenerationThreadItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"result",
|
||||
"status",
|
||||
"type"
|
||||
],
|
||||
"title": "ImageGenerationThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"cwd": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"marketplaceName": {
|
||||
"type": "string"
|
||||
},
|
||||
"pluginName": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"marketplaceName",
|
||||
"pluginName"
|
||||
],
|
||||
"title": "PluginInstallParams",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "PluginInstallResponse",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -641,40 +641,6 @@
|
||||
"title": "WebSearchCallResponseItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"result": {
|
||||
"type": "string"
|
||||
},
|
||||
"revised_prompt": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"image_generation_call"
|
||||
],
|
||||
"title": "ImageGenerationCallResponseItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"result",
|
||||
"status",
|
||||
"type"
|
||||
],
|
||||
"title": "ImageGenerationCallResponseItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"ghost_commit": {
|
||||
|
||||
@@ -977,40 +977,6 @@
|
||||
"title": "ImageViewThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"result": {
|
||||
"type": "string"
|
||||
},
|
||||
"revisedPrompt": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"imageGeneration"
|
||||
],
|
||||
"title": "ImageGenerationThreadItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"result",
|
||||
"status",
|
||||
"type"
|
||||
],
|
||||
"title": "ImageGenerationThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
|
||||
@@ -53,8 +53,7 @@
|
||||
},
|
||||
"ServiceTier": {
|
||||
"enum": [
|
||||
"fast",
|
||||
"flex"
|
||||
"fast"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
|
||||
@@ -744,8 +744,7 @@
|
||||
},
|
||||
"ServiceTier": {
|
||||
"enum": [
|
||||
"fast",
|
||||
"flex"
|
||||
"fast"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
@@ -1453,40 +1452,6 @@
|
||||
"title": "ImageViewThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"result": {
|
||||
"type": "string"
|
||||
},
|
||||
"revisedPrompt": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"imageGeneration"
|
||||
],
|
||||
"title": "ImageGenerationThreadItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"result",
|
||||
"status",
|
||||
"type"
|
||||
],
|
||||
"title": "ImageGenerationThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
|
||||
@@ -1215,40 +1215,6 @@
|
||||
"title": "ImageViewThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"result": {
|
||||
"type": "string"
|
||||
},
|
||||
"revisedPrompt": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"imageGeneration"
|
||||
],
|
||||
"title": "ImageGenerationThreadItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"result",
|
||||
"status",
|
||||
"type"
|
||||
],
|
||||
"title": "ImageGenerationThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
|
||||
@@ -1215,40 +1215,6 @@
|
||||
"title": "ImageViewThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"result": {
|
||||
"type": "string"
|
||||
},
|
||||
"revisedPrompt": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"imageGeneration"
|
||||
],
|
||||
"title": "ImageGenerationThreadItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"result",
|
||||
"status",
|
||||
"type"
|
||||
],
|
||||
"title": "ImageGenerationThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
|
||||
@@ -1215,40 +1215,6 @@
|
||||
"title": "ImageViewThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"result": {
|
||||
"type": "string"
|
||||
},
|
||||
"revisedPrompt": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"imageGeneration"
|
||||
],
|
||||
"title": "ImageGenerationThreadItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"result",
|
||||
"status",
|
||||
"type"
|
||||
],
|
||||
"title": "ImageGenerationThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
|
||||
@@ -691,40 +691,6 @@
|
||||
"title": "WebSearchCallResponseItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"result": {
|
||||
"type": "string"
|
||||
},
|
||||
"revised_prompt": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"image_generation_call"
|
||||
],
|
||||
"title": "ImageGenerationCallResponseItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"result",
|
||||
"status",
|
||||
"type"
|
||||
],
|
||||
"title": "ImageGenerationCallResponseItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"ghost_commit": {
|
||||
@@ -793,8 +759,7 @@
|
||||
},
|
||||
"ServiceTier": {
|
||||
"enum": [
|
||||
"fast",
|
||||
"flex"
|
||||
"fast"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -744,8 +744,7 @@
|
||||
},
|
||||
"ServiceTier": {
|
||||
"enum": [
|
||||
"fast",
|
||||
"flex"
|
||||
"fast"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
@@ -1453,40 +1452,6 @@
|
||||
"title": "ImageViewThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"result": {
|
||||
"type": "string"
|
||||
},
|
||||
"revisedPrompt": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"imageGeneration"
|
||||
],
|
||||
"title": "ImageGenerationThreadItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"result",
|
||||
"status",
|
||||
"type"
|
||||
],
|
||||
"title": "ImageGenerationThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
|
||||
@@ -1215,40 +1215,6 @@
|
||||
"title": "ImageViewThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"result": {
|
||||
"type": "string"
|
||||
},
|
||||
"revisedPrompt": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"imageGeneration"
|
||||
],
|
||||
"title": "ImageGenerationThreadItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"result",
|
||||
"status",
|
||||
"type"
|
||||
],
|
||||
"title": "ImageGenerationThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
|
||||
@@ -78,8 +78,7 @@
|
||||
},
|
||||
"ServiceTier": {
|
||||
"enum": [
|
||||
"fast",
|
||||
"flex"
|
||||
"fast"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
|
||||
@@ -744,8 +744,7 @@
|
||||
},
|
||||
"ServiceTier": {
|
||||
"enum": [
|
||||
"fast",
|
||||
"flex"
|
||||
"fast"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
@@ -1453,40 +1452,6 @@
|
||||
"title": "ImageViewThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"result": {
|
||||
"type": "string"
|
||||
},
|
||||
"revisedPrompt": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"imageGeneration"
|
||||
],
|
||||
"title": "ImageGenerationThreadItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"result",
|
||||
"status",
|
||||
"type"
|
||||
],
|
||||
"title": "ImageGenerationThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
|
||||
@@ -1215,40 +1215,6 @@
|
||||
"title": "ImageViewThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"result": {
|
||||
"type": "string"
|
||||
},
|
||||
"revisedPrompt": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"imageGeneration"
|
||||
],
|
||||
"title": "ImageGenerationThreadItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"result",
|
||||
"status",
|
||||
"type"
|
||||
],
|
||||
"title": "ImageGenerationThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
|
||||
@@ -1215,40 +1215,6 @@
|
||||
"title": "ImageViewThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"result": {
|
||||
"type": "string"
|
||||
},
|
||||
"revisedPrompt": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"imageGeneration"
|
||||
],
|
||||
"title": "ImageGenerationThreadItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"result",
|
||||
"status",
|
||||
"type"
|
||||
],
|
||||
"title": "ImageGenerationThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
|
||||
@@ -977,40 +977,6 @@
|
||||
"title": "ImageViewThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"result": {
|
||||
"type": "string"
|
||||
},
|
||||
"revisedPrompt": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"imageGeneration"
|
||||
],
|
||||
"title": "ImageGenerationThreadItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"result",
|
||||
"status",
|
||||
"type"
|
||||
],
|
||||
"title": "ImageGenerationThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
|
||||
@@ -305,8 +305,7 @@
|
||||
},
|
||||
"ServiceTier": {
|
||||
"enum": [
|
||||
"fast",
|
||||
"flex"
|
||||
"fast"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -977,40 +977,6 @@
|
||||
"title": "ImageViewThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"result": {
|
||||
"type": "string"
|
||||
},
|
||||
"revisedPrompt": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"imageGeneration"
|
||||
],
|
||||
"title": "ImageGenerationThreadItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"result",
|
||||
"status",
|
||||
"type"
|
||||
],
|
||||
"title": "ImageGenerationThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
|
||||
@@ -977,40 +977,6 @@
|
||||
"title": "ImageViewThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"result": {
|
||||
"type": "string"
|
||||
},
|
||||
"revisedPrompt": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"imageGeneration"
|
||||
],
|
||||
"title": "ImageGenerationThreadItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"result",
|
||||
"status",
|
||||
"type"
|
||||
],
|
||||
"title": "ImageGenerationThreadItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
|
||||
@@ -10,12 +10,6 @@
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"cwd": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"mode": {
|
||||
"$ref": "#/definitions/WindowsSandboxSetupMode"
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import type { ListMcpServerStatusParams } from "./v2/ListMcpServerStatusParams";
|
||||
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 { ReviewStartParams } from "./v2/ReviewStartParams";
|
||||
import type { SkillsConfigWriteParams } from "./v2/SkillsConfigWriteParams";
|
||||
import type { SkillsListParams } from "./v2/SkillsListParams";
|
||||
@@ -49,4 +48,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": "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": "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,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", message: string, requested_schema: JsonValue, } | { "mode": "url", message: string, url: string, elicitation_id: string, };
|
||||
@@ -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 { ElicitationRequest } from "./ElicitationRequest";
|
||||
|
||||
export type ElicitationRequestEvent = { server_name: string, id: string | number, request: ElicitationRequest, };
|
||||
export type ElicitationRequestEvent = { server_name: string, id: string | number, message: string, };
|
||||
|
||||
@@ -33,8 +33,6 @@ import type { ExecCommandEndEvent } from "./ExecCommandEndEvent";
|
||||
import type { ExecCommandOutputDeltaEvent } from "./ExecCommandOutputDeltaEvent";
|
||||
import type { ExitedReviewModeEvent } from "./ExitedReviewModeEvent";
|
||||
import type { GetHistoryEntryResponseEvent } from "./GetHistoryEntryResponseEvent";
|
||||
import type { ImageGenerationBeginEvent } from "./ImageGenerationBeginEvent";
|
||||
import type { ImageGenerationEndEvent } from "./ImageGenerationEndEvent";
|
||||
import type { ItemCompletedEvent } from "./ItemCompletedEvent";
|
||||
import type { ItemStartedEvent } from "./ItemStartedEvent";
|
||||
import type { ListCustomPromptsResponseEvent } from "./ListCustomPromptsResponseEvent";
|
||||
@@ -81,4 +79,4 @@ import type { WebSearchEndEvent } from "./WebSearchEndEvent";
|
||||
* Response event from the agent
|
||||
* NOTE: Make sure none of these values have optional types, as it will mess up the extension code-gen.
|
||||
*/
|
||||
export type EventMsg = { "type": "error" } & ErrorEvent | { "type": "warning" } & WarningEvent | { "type": "realtime_conversation_started" } & RealtimeConversationStartedEvent | { "type": "realtime_conversation_realtime" } & RealtimeConversationRealtimeEvent | { "type": "realtime_conversation_closed" } & RealtimeConversationClosedEvent | { "type": "model_reroute" } & ModelRerouteEvent | { "type": "context_compacted" } & ContextCompactedEvent | { "type": "thread_rolled_back" } & ThreadRolledBackEvent | { "type": "task_started" } & TurnStartedEvent | { "type": "task_complete" } & TurnCompleteEvent | { "type": "token_count" } & TokenCountEvent | { "type": "agent_message" } & AgentMessageEvent | { "type": "user_message" } & UserMessageEvent | { "type": "agent_message_delta" } & AgentMessageDeltaEvent | { "type": "agent_reasoning" } & AgentReasoningEvent | { "type": "agent_reasoning_delta" } & AgentReasoningDeltaEvent | { "type": "agent_reasoning_raw_content" } & AgentReasoningRawContentEvent | { "type": "agent_reasoning_raw_content_delta" } & AgentReasoningRawContentDeltaEvent | { "type": "agent_reasoning_section_break" } & AgentReasoningSectionBreakEvent | { "type": "session_configured" } & SessionConfiguredEvent | { "type": "thread_name_updated" } & ThreadNameUpdatedEvent | { "type": "mcp_startup_update" } & McpStartupUpdateEvent | { "type": "mcp_startup_complete" } & McpStartupCompleteEvent | { "type": "mcp_tool_call_begin" } & McpToolCallBeginEvent | { "type": "mcp_tool_call_end" } & McpToolCallEndEvent | { "type": "web_search_begin" } & WebSearchBeginEvent | { "type": "web_search_end" } & WebSearchEndEvent | { "type": "image_generation_begin" } & ImageGenerationBeginEvent | { "type": "image_generation_end" } & ImageGenerationEndEvent | { "type": "exec_command_begin" } & ExecCommandBeginEvent | { "type": "exec_command_output_delta" } & ExecCommandOutputDeltaEvent | { "type": "terminal_interaction" } & TerminalInteractionEvent | { "type": "exec_command_end" } & ExecCommandEndEvent | { "type": "view_image_tool_call" } & ViewImageToolCallEvent | { "type": "exec_approval_request" } & ExecApprovalRequestEvent | { "type": "request_user_input" } & RequestUserInputEvent | { "type": "dynamic_tool_call_request" } & DynamicToolCallRequest | { "type": "dynamic_tool_call_response" } & DynamicToolCallResponseEvent | { "type": "elicitation_request" } & ElicitationRequestEvent | { "type": "apply_patch_approval_request" } & ApplyPatchApprovalRequestEvent | { "type": "deprecation_notice" } & DeprecationNoticeEvent | { "type": "background_event" } & BackgroundEventEvent | { "type": "undo_started" } & UndoStartedEvent | { "type": "undo_completed" } & UndoCompletedEvent | { "type": "stream_error" } & StreamErrorEvent | { "type": "patch_apply_begin" } & PatchApplyBeginEvent | { "type": "patch_apply_end" } & PatchApplyEndEvent | { "type": "turn_diff" } & TurnDiffEvent | { "type": "get_history_entry_response" } & GetHistoryEntryResponseEvent | { "type": "mcp_list_tools_response" } & McpListToolsResponseEvent | { "type": "list_custom_prompts_response" } & ListCustomPromptsResponseEvent | { "type": "list_skills_response" } & ListSkillsResponseEvent | { "type": "list_remote_skills_response" } & ListRemoteSkillsResponseEvent | { "type": "remote_skill_downloaded" } & RemoteSkillDownloadedEvent | { "type": "skills_update_available" } | { "type": "plan_update" } & UpdatePlanArgs | { "type": "turn_aborted" } & TurnAbortedEvent | { "type": "shutdown_complete" } | { "type": "entered_review_mode" } & ReviewRequest | { "type": "exited_review_mode" } & ExitedReviewModeEvent | { "type": "raw_response_item" } & RawResponseItemEvent | { "type": "item_started" } & ItemStartedEvent | { "type": "item_completed" } & ItemCompletedEvent | { "type": "agent_message_content_delta" } & AgentMessageContentDeltaEvent | { "type": "plan_delta" } & PlanDeltaEvent | { "type": "reasoning_content_delta" } & ReasoningContentDeltaEvent | { "type": "reasoning_raw_content_delta" } & ReasoningRawContentDeltaEvent | { "type": "collab_agent_spawn_begin" } & CollabAgentSpawnBeginEvent | { "type": "collab_agent_spawn_end" } & CollabAgentSpawnEndEvent | { "type": "collab_agent_interaction_begin" } & CollabAgentInteractionBeginEvent | { "type": "collab_agent_interaction_end" } & CollabAgentInteractionEndEvent | { "type": "collab_waiting_begin" } & CollabWaitingBeginEvent | { "type": "collab_waiting_end" } & CollabWaitingEndEvent | { "type": "collab_close_begin" } & CollabCloseBeginEvent | { "type": "collab_close_end" } & CollabCloseEndEvent | { "type": "collab_resume_begin" } & CollabResumeBeginEvent | { "type": "collab_resume_end" } & CollabResumeEndEvent;
|
||||
export type EventMsg = { "type": "error" } & ErrorEvent | { "type": "warning" } & WarningEvent | { "type": "realtime_conversation_started" } & RealtimeConversationStartedEvent | { "type": "realtime_conversation_realtime" } & RealtimeConversationRealtimeEvent | { "type": "realtime_conversation_closed" } & RealtimeConversationClosedEvent | { "type": "model_reroute" } & ModelRerouteEvent | { "type": "context_compacted" } & ContextCompactedEvent | { "type": "thread_rolled_back" } & ThreadRolledBackEvent | { "type": "task_started" } & TurnStartedEvent | { "type": "task_complete" } & TurnCompleteEvent | { "type": "token_count" } & TokenCountEvent | { "type": "agent_message" } & AgentMessageEvent | { "type": "user_message" } & UserMessageEvent | { "type": "agent_message_delta" } & AgentMessageDeltaEvent | { "type": "agent_reasoning" } & AgentReasoningEvent | { "type": "agent_reasoning_delta" } & AgentReasoningDeltaEvent | { "type": "agent_reasoning_raw_content" } & AgentReasoningRawContentEvent | { "type": "agent_reasoning_raw_content_delta" } & AgentReasoningRawContentDeltaEvent | { "type": "agent_reasoning_section_break" } & AgentReasoningSectionBreakEvent | { "type": "session_configured" } & SessionConfiguredEvent | { "type": "thread_name_updated" } & ThreadNameUpdatedEvent | { "type": "mcp_startup_update" } & McpStartupUpdateEvent | { "type": "mcp_startup_complete" } & McpStartupCompleteEvent | { "type": "mcp_tool_call_begin" } & McpToolCallBeginEvent | { "type": "mcp_tool_call_end" } & McpToolCallEndEvent | { "type": "web_search_begin" } & WebSearchBeginEvent | { "type": "web_search_end" } & WebSearchEndEvent | { "type": "exec_command_begin" } & ExecCommandBeginEvent | { "type": "exec_command_output_delta" } & ExecCommandOutputDeltaEvent | { "type": "terminal_interaction" } & TerminalInteractionEvent | { "type": "exec_command_end" } & ExecCommandEndEvent | { "type": "view_image_tool_call" } & ViewImageToolCallEvent | { "type": "exec_approval_request" } & ExecApprovalRequestEvent | { "type": "request_user_input" } & RequestUserInputEvent | { "type": "dynamic_tool_call_request" } & DynamicToolCallRequest | { "type": "dynamic_tool_call_response" } & DynamicToolCallResponseEvent | { "type": "elicitation_request" } & ElicitationRequestEvent | { "type": "apply_patch_approval_request" } & ApplyPatchApprovalRequestEvent | { "type": "deprecation_notice" } & DeprecationNoticeEvent | { "type": "background_event" } & BackgroundEventEvent | { "type": "undo_started" } & UndoStartedEvent | { "type": "undo_completed" } & UndoCompletedEvent | { "type": "stream_error" } & StreamErrorEvent | { "type": "patch_apply_begin" } & PatchApplyBeginEvent | { "type": "patch_apply_end" } & PatchApplyEndEvent | { "type": "turn_diff" } & TurnDiffEvent | { "type": "get_history_entry_response" } & GetHistoryEntryResponseEvent | { "type": "mcp_list_tools_response" } & McpListToolsResponseEvent | { "type": "list_custom_prompts_response" } & ListCustomPromptsResponseEvent | { "type": "list_skills_response" } & ListSkillsResponseEvent | { "type": "list_remote_skills_response" } & ListRemoteSkillsResponseEvent | { "type": "remote_skill_downloaded" } & RemoteSkillDownloadedEvent | { "type": "skills_update_available" } | { "type": "plan_update" } & UpdatePlanArgs | { "type": "turn_aborted" } & TurnAbortedEvent | { "type": "shutdown_complete" } | { "type": "entered_review_mode" } & ReviewRequest | { "type": "exited_review_mode" } & ExitedReviewModeEvent | { "type": "raw_response_item" } & RawResponseItemEvent | { "type": "item_started" } & ItemStartedEvent | { "type": "item_completed" } & ItemCompletedEvent | { "type": "agent_message_content_delta" } & AgentMessageContentDeltaEvent | { "type": "plan_delta" } & PlanDeltaEvent | { "type": "reasoning_content_delta" } & ReasoningContentDeltaEvent | { "type": "reasoning_raw_content_delta" } & ReasoningRawContentDeltaEvent | { "type": "collab_agent_spawn_begin" } & CollabAgentSpawnBeginEvent | { "type": "collab_agent_spawn_end" } & CollabAgentSpawnEndEvent | { "type": "collab_agent_interaction_begin" } & CollabAgentInteractionBeginEvent | { "type": "collab_agent_interaction_end" } & CollabAgentInteractionEndEvent | { "type": "collab_waiting_begin" } & CollabWaitingBeginEvent | { "type": "collab_waiting_end" } & CollabWaitingEndEvent | { "type": "collab_close_begin" } & CollabCloseBeginEvent | { "type": "collab_close_end" } & CollabCloseEndEvent | { "type": "collab_resume_begin" } & CollabResumeBeginEvent | { "type": "collab_resume_end" } & CollabResumeEndEvent;
|
||||
|
||||
@@ -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 ImageGenerationEndEvent = { call_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 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 ImageGenerationBeginEvent = { call_id: string, };
|
||||
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 PluginInstallResponse = Record<string, never>;
|
||||
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, };
|
||||
|
||||
@@ -15,4 +15,4 @@ export type ResponseItem = { "type": "message", role: string, content: Array<Con
|
||||
/**
|
||||
* Set when using the Responses API.
|
||||
*/
|
||||
call_id: string | null, status: LocalShellStatus, action: LocalShellAction, } | { "type": "function_call", name: string, arguments: string, call_id: string, } | { "type": "function_call_output", call_id: string, output: FunctionCallOutputPayload, } | { "type": "custom_tool_call", status?: string, call_id: string, name: string, input: string, } | { "type": "custom_tool_call_output", call_id: string, output: FunctionCallOutputPayload, } | { "type": "web_search_call", status?: string, action?: WebSearchAction, } | { "type": "image_generation_call", id: string, status: string, revised_prompt?: string, result: string, } | { "type": "ghost_snapshot", ghost_commit: GhostCommit, } | { "type": "compaction", encrypted_content: string, } | { "type": "other" };
|
||||
call_id: string | null, status: LocalShellStatus, action: LocalShellAction, } | { "type": "function_call", name: string, arguments: string, call_id: string, } | { "type": "function_call_output", call_id: string, output: FunctionCallOutputPayload, } | { "type": "custom_tool_call", status?: string, call_id: string, name: string, input: string, } | { "type": "custom_tool_call_output", call_id: string, output: FunctionCallOutputPayload, } | { "type": "web_search_call", status?: string, action?: WebSearchAction, } | { "type": "ghost_snapshot", ghost_commit: GhostCommit, } | { "type": "compaction", encrypted_content: string, } | { "type": "other" };
|
||||
|
||||
@@ -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 ServiceTier = "fast" | "flex";
|
||||
export type ServiceTier = "fast";
|
||||
|
||||
@@ -3,10 +3,9 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { AgentMessageItem } from "./AgentMessageItem";
|
||||
import type { ContextCompactionItem } from "./ContextCompactionItem";
|
||||
import type { ImageGenerationItem } from "./ImageGenerationItem";
|
||||
import type { PlanItem } from "./PlanItem";
|
||||
import type { ReasoningItem } from "./ReasoningItem";
|
||||
import type { UserMessageItem } from "./UserMessageItem";
|
||||
import type { WebSearchItem } from "./WebSearchItem";
|
||||
|
||||
export type TurnItem = { "type": "UserMessage" } & UserMessageItem | { "type": "AgentMessage" } & AgentMessageItem | { "type": "Plan" } & PlanItem | { "type": "Reasoning" } & ReasoningItem | { "type": "WebSearch" } & WebSearchItem | { "type": "ImageGeneration" } & ImageGenerationItem | { "type": "ContextCompaction" } & ContextCompactionItem;
|
||||
export type TurnItem = { "type": "UserMessage" } & UserMessageItem | { "type": "AgentMessage" } & AgentMessageItem | { "type": "Plan" } & PlanItem | { "type": "Reasoning" } & ReasoningItem | { "type": "WebSearch" } & WebSearchItem | { "type": "ContextCompaction" } & ContextCompactionItem;
|
||||
|
||||
@@ -48,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";
|
||||
@@ -85,9 +84,6 @@ export type { GitDiffToRemoteResponse } from "./GitDiffToRemoteResponse";
|
||||
export type { GitSha } from "./GitSha";
|
||||
export type { HistoryEntry } from "./HistoryEntry";
|
||||
export type { ImageDetail } from "./ImageDetail";
|
||||
export type { ImageGenerationBeginEvent } from "./ImageGenerationBeginEvent";
|
||||
export type { ImageGenerationEndEvent } from "./ImageGenerationEndEvent";
|
||||
export type { ImageGenerationItem } from "./ImageGenerationItem";
|
||||
export type { InitializeCapabilities } from "./InitializeCapabilities";
|
||||
export type { InitializeParams } from "./InitializeParams";
|
||||
export type { InitializeResponse } from "./InitializeResponse";
|
||||
@@ -100,9 +96,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,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,15 +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 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", message: string, requestedSchema: JsonValue, } | { "mode": "url", message: string, url: string, elicitationId: string, });
|
||||
@@ -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 { 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, };
|
||||
@@ -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 PluginInstallParams = { marketplaceName: string, pluginName: string, cwd?: string | null, };
|
||||
@@ -85,4 +85,4 @@ prompt: string | null,
|
||||
/**
|
||||
* Last known status of the target agents, when available.
|
||||
*/
|
||||
agentsStates: { [key in string]?: CollabAgentState }, } | { "type": "webSearch", id: string, query: string, action: WebSearchAction | null, } | { "type": "imageView", id: string, path: string, } | { "type": "imageGeneration", id: string, status: string, revisedPrompt: string | null, result: string, } | { "type": "enteredReviewMode", id: string, review: string, } | { "type": "exitedReviewMode", id: string, review: string, } | { "type": "contextCompaction", id: string, };
|
||||
agentsStates: { [key in string]?: CollabAgentState }, } | { "type": "webSearch", id: string, query: string, action: WebSearchAction | null, } | { "type": "imageView", id: string, path: string, } | { "type": "enteredReviewMode", id: string, review: string, } | { "type": "exitedReviewMode", id: string, review: string, } | { "type": "contextCompaction", id: string, };
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { WindowsSandboxSetupMode } from "./WindowsSandboxSetupMode";
|
||||
|
||||
export type WindowsSandboxSetupStartParams = { mode: WindowsSandboxSetupMode, cwd?: string | null, };
|
||||
export type WindowsSandboxSetupStartParams = { mode: WindowsSandboxSetupMode, };
|
||||
|
||||
@@ -97,9 +97,6 @@ export type { LoginAccountParams } from "./LoginAccountParams";
|
||||
export type { LoginAccountResponse } from "./LoginAccountResponse";
|
||||
export type { LogoutAccountResponse } from "./LogoutAccountResponse";
|
||||
export type { McpAuthStatus } from "./McpAuthStatus";
|
||||
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";
|
||||
@@ -127,8 +124,6 @@ export type { OverriddenMetadata } from "./OverriddenMetadata";
|
||||
export type { PatchApplyStatus } from "./PatchApplyStatus";
|
||||
export type { PatchChangeKind } from "./PatchChangeKind";
|
||||
export type { PlanDeltaNotification } from "./PlanDeltaNotification";
|
||||
export type { PluginInstallParams } from "./PluginInstallParams";
|
||||
export type { PluginInstallResponse } from "./PluginInstallResponse";
|
||||
export type { ProductSurface } from "./ProductSurface";
|
||||
export type { ProfileV2 } from "./ProfileV2";
|
||||
export type { RateLimitSnapshot } from "./RateLimitSnapshot";
|
||||
|
||||
@@ -264,10 +264,6 @@ client_request_definitions! {
|
||||
params: v2::SkillsConfigWriteParams,
|
||||
response: v2::SkillsConfigWriteResponse,
|
||||
},
|
||||
PluginInstall => "plugin/install" {
|
||||
params: v2::PluginInstallParams,
|
||||
response: v2::PluginInstallResponse,
|
||||
},
|
||||
TurnStart => "turn/start" {
|
||||
params: v2::TurnStartParams,
|
||||
inspect_params: true,
|
||||
@@ -650,12 +646,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,
|
||||
@@ -1052,60 +1042,6 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_mcp_server_elicitation_request() -> Result<()> {
|
||||
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 {
|
||||
message: "Allow this request?".to_string(),
|
||||
requested_schema: json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"confirmed": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": ["confirmed"]
|
||||
}),
|
||||
},
|
||||
};
|
||||
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",
|
||||
"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 {
|
||||
|
||||
@@ -30,8 +30,6 @@ use codex_protocol::protocol::ErrorEvent;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::ExecCommandBeginEvent;
|
||||
use codex_protocol::protocol::ExecCommandEndEvent;
|
||||
use codex_protocol::protocol::ImageGenerationBeginEvent;
|
||||
use codex_protocol::protocol::ImageGenerationEndEvent;
|
||||
use codex_protocol::protocol::ItemCompletedEvent;
|
||||
use codex_protocol::protocol::ItemStartedEvent;
|
||||
use codex_protocol::protocol::McpToolCallBeginEvent;
|
||||
@@ -143,8 +141,6 @@ impl ThreadHistoryBuilder {
|
||||
EventMsg::McpToolCallBegin(payload) => self.handle_mcp_tool_call_begin(payload),
|
||||
EventMsg::McpToolCallEnd(payload) => self.handle_mcp_tool_call_end(payload),
|
||||
EventMsg::ViewImageToolCall(payload) => self.handle_view_image_tool_call(payload),
|
||||
EventMsg::ImageGenerationBegin(payload) => self.handle_image_generation_begin(payload),
|
||||
EventMsg::ImageGenerationEnd(payload) => self.handle_image_generation_end(payload),
|
||||
EventMsg::CollabAgentSpawnBegin(payload) => {
|
||||
self.handle_collab_agent_spawn_begin(payload)
|
||||
}
|
||||
@@ -273,7 +269,6 @@ impl ThreadHistoryBuilder {
|
||||
| codex_protocol::items::TurnItem::AgentMessage(_)
|
||||
| codex_protocol::items::TurnItem::Reasoning(_)
|
||||
| codex_protocol::items::TurnItem::WebSearch(_)
|
||||
| codex_protocol::items::TurnItem::ImageGeneration(_)
|
||||
| codex_protocol::items::TurnItem::ContextCompaction(_) => {}
|
||||
}
|
||||
}
|
||||
@@ -293,7 +288,6 @@ impl ThreadHistoryBuilder {
|
||||
| codex_protocol::items::TurnItem::AgentMessage(_)
|
||||
| codex_protocol::items::TurnItem::Reasoning(_)
|
||||
| codex_protocol::items::TurnItem::WebSearch(_)
|
||||
| codex_protocol::items::TurnItem::ImageGeneration(_)
|
||||
| codex_protocol::items::TurnItem::ContextCompaction(_) => {}
|
||||
}
|
||||
}
|
||||
@@ -522,26 +516,6 @@ impl ThreadHistoryBuilder {
|
||||
self.upsert_item_in_current_turn(item);
|
||||
}
|
||||
|
||||
fn handle_image_generation_begin(&mut self, payload: &ImageGenerationBeginEvent) {
|
||||
let item = ThreadItem::ImageGeneration {
|
||||
id: payload.call_id.clone(),
|
||||
status: String::new(),
|
||||
revised_prompt: None,
|
||||
result: String::new(),
|
||||
};
|
||||
self.upsert_item_in_current_turn(item);
|
||||
}
|
||||
|
||||
fn handle_image_generation_end(&mut self, payload: &ImageGenerationEndEvent) {
|
||||
let item = ThreadItem::ImageGeneration {
|
||||
id: payload.call_id.clone(),
|
||||
status: payload.status.clone(),
|
||||
revised_prompt: payload.revised_prompt.clone(),
|
||||
result: payload.result.clone(),
|
||||
};
|
||||
self.upsert_item_in_current_turn(item);
|
||||
}
|
||||
|
||||
fn handle_collab_agent_spawn_begin(
|
||||
&mut self,
|
||||
payload: &codex_protocol::protocol::CollabAgentSpawnBeginEvent,
|
||||
|
||||
@@ -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;
|
||||
@@ -638,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,
|
||||
@@ -837,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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1709,8 +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)]
|
||||
@@ -2396,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,
|
||||
@@ -2549,21 +2546,6 @@ pub struct SkillsConfigWriteResponse {
|
||||
pub effective_enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct PluginInstallParams {
|
||||
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 {}
|
||||
|
||||
impl From<CoreSkillMetadata> for SkillMetadata {
|
||||
fn from(value: CoreSkillMetadata) -> Self {
|
||||
Self {
|
||||
@@ -3350,14 +3332,6 @@ pub enum ThreadItem {
|
||||
ImageView { id: String, path: String },
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
ImageGeneration {
|
||||
id: String,
|
||||
status: String,
|
||||
revised_prompt: Option<String>,
|
||||
result: String,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
EnteredReviewMode { id: String, review: String },
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
@@ -3381,7 +3355,6 @@ impl ThreadItem {
|
||||
| ThreadItem::CollabAgentToolCall { id, .. }
|
||||
| ThreadItem::WebSearch { id, .. }
|
||||
| ThreadItem::ImageView { id, .. }
|
||||
| ThreadItem::ImageGeneration { id, .. }
|
||||
| ThreadItem::EnteredReviewMode { id, .. }
|
||||
| ThreadItem::ExitedReviewMode { id, .. }
|
||||
| ThreadItem::ContextCompaction { id, .. } => id,
|
||||
@@ -3461,12 +3434,6 @@ impl From<CoreTurnItem> for ThreadItem {
|
||||
query: search.query,
|
||||
action: Some(WebSearchAction::from(search.action)),
|
||||
},
|
||||
CoreTurnItem::ImageGeneration(image) => ThreadItem::ImageGeneration {
|
||||
id: image.id,
|
||||
status: image.status,
|
||||
revised_prompt: image.revised_prompt,
|
||||
result: image.result,
|
||||
},
|
||||
CoreTurnItem::ContextCompaction(compaction) => {
|
||||
ThreadItem::ContextCompaction { id: compaction.id }
|
||||
}
|
||||
@@ -3960,8 +3927,6 @@ pub enum WindowsSandboxSetupMode {
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct WindowsSandboxSetupStartParams {
|
||||
pub mode: WindowsSandboxSetupMode,
|
||||
#[ts(optional = nullable)]
|
||||
pub cwd: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
@@ -4084,138 +4049,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.
|
||||
}
|
||||
|
||||
#[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 {
|
||||
message: String,
|
||||
requested_schema: JsonValue,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
Url {
|
||||
message: String,
|
||||
url: String,
|
||||
elicitation_id: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<CoreElicitationRequest> for McpServerElicitationRequest {
|
||||
fn from(value: CoreElicitationRequest) -> Self {
|
||||
match value {
|
||||
CoreElicitationRequest::Form {
|
||||
message,
|
||||
requested_schema,
|
||||
} => Self::Form {
|
||||
message,
|
||||
requested_schema,
|
||||
},
|
||||
CoreElicitationRequest::Url {
|
||||
message,
|
||||
url,
|
||||
elicitation_id,
|
||||
} => Self::Url {
|
||||
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>,
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
@@ -4552,65 +4385,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,
|
||||
})),
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
rmcp::model::CreateElicitationResult::from(v2_response),
|
||||
rmcp_result
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mcp_server_elicitation_request_from_core_url_request() {
|
||||
let request = McpServerElicitationRequest::from(CoreElicitationRequest::Url {
|
||||
message: "Finish sign-in".to_string(),
|
||||
url: "https://example.com/complete".to_string(),
|
||||
elicitation_id: "elicitation-123".to_string(),
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
request,
|
||||
McpServerElicitationRequest::Url {
|
||||
message: "Finish sign-in".to_string(),
|
||||
url: "https://example.com/complete".to_string(),
|
||||
elicitation_id: "elicitation-123".to_string(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mcp_server_elicitation_response_serializes_nullable_content() {
|
||||
let response = McpServerElicitationRequestResponse {
|
||||
action: McpServerElicitationAction::Decline,
|
||||
content: None,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
serde_json::to_value(response).expect("response should serialize"),
|
||||
json!({
|
||||
"action": "decline",
|
||||
"content": null,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sandbox_policy_round_trips_workspace_write_read_only_access() {
|
||||
let readable_root = test_absolute_path();
|
||||
|
||||
@@ -11,15 +11,9 @@ workspace = true
|
||||
anyhow = { workspace = true }
|
||||
clap = { workspace = true, features = ["derive", "env"] }
|
||||
codex-app-server-protocol = { workspace = true }
|
||||
codex-core = { workspace = true }
|
||||
codex-otel = { workspace = true }
|
||||
codex-protocol = { workspace = true }
|
||||
codex-utils-cli = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
tokio = { workspace = true, features = ["rt"] }
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true }
|
||||
tungstenite = { workspace = true }
|
||||
url = { workspace = true }
|
||||
uuid = { workspace = true, features = ["v4"] }
|
||||
|
||||
@@ -62,17 +62,10 @@ 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::config::Config;
|
||||
use codex_otel::current_span_w3c_trace_context;
|
||||
use codex_otel::otel_provider::OtelProvider;
|
||||
use codex_protocol::protocol::W3cTraceContext;
|
||||
use codex_utils_cli::CliConfigOverrides;
|
||||
use serde::Serialize;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json::Value;
|
||||
use tracing::info_span;
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
use tungstenite::Message;
|
||||
use tungstenite::WebSocket;
|
||||
use tungstenite::connect;
|
||||
@@ -105,10 +98,6 @@ const NOTIFICATIONS_TO_OPT_OUT: &[&str] = &[
|
||||
];
|
||||
const APP_SERVER_GRACEFUL_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5);
|
||||
const APP_SERVER_GRACEFUL_SHUTDOWN_POLL_INTERVAL: Duration = Duration::from_millis(100);
|
||||
const DEFAULT_ANALYTICS_ENABLED: bool = true;
|
||||
const OTEL_SERVICE_NAME: &str = "codex-app-server-test-client";
|
||||
const TRACE_DISABLED_MESSAGE: &str =
|
||||
"Not enabled - enable tracing in $CODEX_HOME/config.toml to get a trace URL!";
|
||||
|
||||
/// Minimal launcher that initializes the Codex app-server and logs the handshake.
|
||||
#[derive(Parser)]
|
||||
@@ -247,7 +236,7 @@ enum CliCommand {
|
||||
},
|
||||
}
|
||||
|
||||
pub async fn run() -> Result<()> {
|
||||
pub fn run() -> Result<()> {
|
||||
let Cli {
|
||||
codex_bin,
|
||||
url,
|
||||
@@ -267,7 +256,7 @@ pub async fn run() -> Result<()> {
|
||||
CliCommand::SendMessage { user_message } => {
|
||||
ensure_dynamic_tools_unused(&dynamic_tools, "send-message")?;
|
||||
let endpoint = resolve_endpoint(codex_bin, url)?;
|
||||
send_message(&endpoint, &config_overrides, user_message).await
|
||||
send_message(&endpoint, &config_overrides, user_message)
|
||||
}
|
||||
CliCommand::SendMessageV2 {
|
||||
experimental_api,
|
||||
@@ -281,7 +270,6 @@ pub async fn run() -> Result<()> {
|
||||
experimental_api,
|
||||
&dynamic_tools,
|
||||
)
|
||||
.await
|
||||
}
|
||||
CliCommand::ResumeMessageV2 {
|
||||
thread_id,
|
||||
@@ -295,29 +283,28 @@ pub async fn run() -> Result<()> {
|
||||
user_message,
|
||||
&dynamic_tools,
|
||||
)
|
||||
.await
|
||||
}
|
||||
CliCommand::ThreadResume { thread_id } => {
|
||||
ensure_dynamic_tools_unused(&dynamic_tools, "thread-resume")?;
|
||||
let endpoint = resolve_endpoint(codex_bin, url)?;
|
||||
thread_resume_follow(&endpoint, &config_overrides, thread_id).await
|
||||
thread_resume_follow(&endpoint, &config_overrides, thread_id)
|
||||
}
|
||||
CliCommand::Watch => {
|
||||
ensure_dynamic_tools_unused(&dynamic_tools, "watch")?;
|
||||
let endpoint = resolve_endpoint(codex_bin, url)?;
|
||||
watch(&endpoint, &config_overrides).await
|
||||
watch(&endpoint, &config_overrides)
|
||||
}
|
||||
CliCommand::TriggerCmdApproval { user_message } => {
|
||||
let endpoint = resolve_endpoint(codex_bin, url)?;
|
||||
trigger_cmd_approval(&endpoint, &config_overrides, user_message, &dynamic_tools).await
|
||||
trigger_cmd_approval(&endpoint, &config_overrides, user_message, &dynamic_tools)
|
||||
}
|
||||
CliCommand::TriggerPatchApproval { user_message } => {
|
||||
let endpoint = resolve_endpoint(codex_bin, url)?;
|
||||
trigger_patch_approval(&endpoint, &config_overrides, user_message, &dynamic_tools).await
|
||||
trigger_patch_approval(&endpoint, &config_overrides, user_message, &dynamic_tools)
|
||||
}
|
||||
CliCommand::NoTriggerCmdApproval => {
|
||||
let endpoint = resolve_endpoint(codex_bin, url)?;
|
||||
no_trigger_cmd_approval(&endpoint, &config_overrides, &dynamic_tools).await
|
||||
no_trigger_cmd_approval(&endpoint, &config_overrides, &dynamic_tools)
|
||||
}
|
||||
CliCommand::SendFollowUpV2 {
|
||||
first_message,
|
||||
@@ -331,7 +318,6 @@ pub async fn run() -> Result<()> {
|
||||
follow_up_message,
|
||||
&dynamic_tools,
|
||||
)
|
||||
.await
|
||||
}
|
||||
CliCommand::TriggerZshForkMultiCmdApproval {
|
||||
user_message,
|
||||
@@ -347,27 +333,26 @@ pub async fn run() -> Result<()> {
|
||||
abort_on,
|
||||
&dynamic_tools,
|
||||
)
|
||||
.await
|
||||
}
|
||||
CliCommand::TestLogin => {
|
||||
ensure_dynamic_tools_unused(&dynamic_tools, "test-login")?;
|
||||
let endpoint = resolve_endpoint(codex_bin, url)?;
|
||||
test_login(&endpoint, &config_overrides).await
|
||||
test_login(&endpoint, &config_overrides)
|
||||
}
|
||||
CliCommand::GetAccountRateLimits => {
|
||||
ensure_dynamic_tools_unused(&dynamic_tools, "get-account-rate-limits")?;
|
||||
let endpoint = resolve_endpoint(codex_bin, url)?;
|
||||
get_account_rate_limits(&endpoint, &config_overrides).await
|
||||
get_account_rate_limits(&endpoint, &config_overrides)
|
||||
}
|
||||
CliCommand::ModelList => {
|
||||
ensure_dynamic_tools_unused(&dynamic_tools, "model-list")?;
|
||||
let endpoint = resolve_endpoint(codex_bin, url)?;
|
||||
model_list(&endpoint, &config_overrides).await
|
||||
model_list(&endpoint, &config_overrides)
|
||||
}
|
||||
CliCommand::ThreadList { limit } => {
|
||||
ensure_dynamic_tools_unused(&dynamic_tools, "thread-list")?;
|
||||
let endpoint = resolve_endpoint(codex_bin, url)?;
|
||||
thread_list(&endpoint, &config_overrides, limit).await
|
||||
thread_list(&endpoint, &config_overrides, limit)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -502,15 +487,7 @@ fn shell_quote(input: &str) -> String {
|
||||
format!("'{}'", input.replace('\'', "'\\''"))
|
||||
}
|
||||
|
||||
struct SendMessagePolicies<'a> {
|
||||
command_name: &'static str,
|
||||
experimental_api: bool,
|
||||
approval_policy: Option<AskForApproval>,
|
||||
sandbox_policy: Option<SandboxPolicy>,
|
||||
dynamic_tools: &'a Option<Vec<DynamicToolSpec>>,
|
||||
}
|
||||
|
||||
async fn send_message(
|
||||
fn send_message(
|
||||
endpoint: &Endpoint,
|
||||
config_overrides: &[String],
|
||||
user_message: String,
|
||||
@@ -520,18 +497,14 @@ async fn send_message(
|
||||
endpoint,
|
||||
config_overrides,
|
||||
user_message,
|
||||
SendMessagePolicies {
|
||||
command_name: "send-message",
|
||||
experimental_api: false,
|
||||
approval_policy: None,
|
||||
sandbox_policy: None,
|
||||
dynamic_tools: &dynamic_tools,
|
||||
},
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
&dynamic_tools,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn send_message_v2(
|
||||
pub fn send_message_v2(
|
||||
codex_bin: &Path,
|
||||
config_overrides: &[String],
|
||||
user_message: String,
|
||||
@@ -545,10 +518,9 @@ pub async fn send_message_v2(
|
||||
true,
|
||||
dynamic_tools,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn send_message_v2_endpoint(
|
||||
fn send_message_v2_endpoint(
|
||||
endpoint: &Endpoint,
|
||||
config_overrides: &[String],
|
||||
user_message: String,
|
||||
@@ -563,18 +535,14 @@ async fn send_message_v2_endpoint(
|
||||
endpoint,
|
||||
config_overrides,
|
||||
user_message,
|
||||
SendMessagePolicies {
|
||||
command_name: "send-message-v2",
|
||||
experimental_api,
|
||||
approval_policy: None,
|
||||
sandbox_policy: None,
|
||||
dynamic_tools,
|
||||
},
|
||||
experimental_api,
|
||||
None,
|
||||
None,
|
||||
dynamic_tools,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn trigger_zsh_fork_multi_cmd_approval(
|
||||
fn trigger_zsh_fork_multi_cmd_approval(
|
||||
endpoint: &Endpoint,
|
||||
config_overrides: &[String],
|
||||
user_message: Option<String>,
|
||||
@@ -591,96 +559,89 @@ async fn trigger_zsh_fork_multi_cmd_approval(
|
||||
let default_prompt = "Run this exact command using shell command execution without rewriting or splitting it: /usr/bin/true && /usr/bin/true";
|
||||
let message = user_message.unwrap_or_else(|| default_prompt.to_string());
|
||||
|
||||
with_client(
|
||||
"trigger-zsh-fork-multi-cmd-approval",
|
||||
endpoint,
|
||||
config_overrides,
|
||||
|client| {
|
||||
let initialize = client.initialize()?;
|
||||
println!("< initialize response: {initialize:?}");
|
||||
with_client(endpoint, config_overrides, |client| {
|
||||
let initialize = client.initialize()?;
|
||||
println!("< initialize response: {initialize:?}");
|
||||
|
||||
let thread_response = client.thread_start(ThreadStartParams {
|
||||
dynamic_tools: dynamic_tools.clone(),
|
||||
..Default::default()
|
||||
})?;
|
||||
println!("< thread/start response: {thread_response:?}");
|
||||
let thread_response = client.thread_start(ThreadStartParams {
|
||||
dynamic_tools: dynamic_tools.clone(),
|
||||
..Default::default()
|
||||
})?;
|
||||
println!("< thread/start response: {thread_response:?}");
|
||||
|
||||
client.command_approval_behavior = match abort_on {
|
||||
Some(index) => CommandApprovalBehavior::AbortOn(index),
|
||||
None => CommandApprovalBehavior::AlwaysAccept,
|
||||
};
|
||||
client.command_approval_count = 0;
|
||||
client.command_approval_item_ids.clear();
|
||||
client.command_execution_statuses.clear();
|
||||
client.last_turn_status = None;
|
||||
client.command_approval_behavior = match abort_on {
|
||||
Some(index) => CommandApprovalBehavior::AbortOn(index),
|
||||
None => CommandApprovalBehavior::AlwaysAccept,
|
||||
};
|
||||
client.command_approval_count = 0;
|
||||
client.command_approval_item_ids.clear();
|
||||
client.command_execution_statuses.clear();
|
||||
client.last_turn_status = None;
|
||||
|
||||
let mut turn_params = TurnStartParams {
|
||||
thread_id: thread_response.thread.id.clone(),
|
||||
input: vec![V2UserInput::Text {
|
||||
text: message,
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
turn_params.approval_policy = Some(AskForApproval::OnRequest);
|
||||
turn_params.sandbox_policy = Some(SandboxPolicy::ReadOnly {
|
||||
access: ReadOnlyAccess::FullAccess,
|
||||
network_access: false,
|
||||
});
|
||||
let mut turn_params = TurnStartParams {
|
||||
thread_id: thread_response.thread.id.clone(),
|
||||
input: vec![V2UserInput::Text {
|
||||
text: message,
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
turn_params.approval_policy = Some(AskForApproval::OnRequest);
|
||||
turn_params.sandbox_policy = Some(SandboxPolicy::ReadOnly {
|
||||
access: ReadOnlyAccess::FullAccess,
|
||||
network_access: false,
|
||||
});
|
||||
|
||||
let turn_response = client.turn_start(turn_params)?;
|
||||
println!("< turn/start response: {turn_response:?}");
|
||||
client.stream_turn(&thread_response.thread.id, &turn_response.turn.id)?;
|
||||
let turn_response = client.turn_start(turn_params)?;
|
||||
println!("< turn/start response: {turn_response:?}");
|
||||
client.stream_turn(&thread_response.thread.id, &turn_response.turn.id)?;
|
||||
|
||||
if client.command_approval_count < min_approvals {
|
||||
bail!(
|
||||
"expected at least {min_approvals} command approvals, got {}",
|
||||
client.command_approval_count
|
||||
);
|
||||
}
|
||||
let mut approvals_per_item = std::collections::BTreeMap::new();
|
||||
for item_id in &client.command_approval_item_ids {
|
||||
*approvals_per_item.entry(item_id.clone()).or_insert(0usize) += 1;
|
||||
}
|
||||
let max_approvals_for_one_item =
|
||||
approvals_per_item.values().copied().max().unwrap_or(0);
|
||||
if max_approvals_for_one_item < min_approvals {
|
||||
bail!(
|
||||
"expected at least {min_approvals} approvals for one command item, got max {max_approvals_for_one_item} with map {approvals_per_item:?}"
|
||||
);
|
||||
}
|
||||
|
||||
let last_command_status = client.command_execution_statuses.last();
|
||||
if abort_on.is_none() {
|
||||
if last_command_status != Some(&CommandExecutionStatus::Completed) {
|
||||
bail!("expected completed command execution, got {last_command_status:?}");
|
||||
}
|
||||
if client.last_turn_status != Some(TurnStatus::Completed) {
|
||||
bail!(
|
||||
"expected completed turn in all-accept flow, got {:?}",
|
||||
client.last_turn_status
|
||||
);
|
||||
}
|
||||
} else if last_command_status == Some(&CommandExecutionStatus::Completed) {
|
||||
bail!(
|
||||
"expected non-completed command execution in mixed approval/decline flow, got {last_command_status:?}"
|
||||
);
|
||||
}
|
||||
|
||||
println!(
|
||||
"[zsh-fork multi-approval summary] approvals={}, approvals_per_item={approvals_per_item:?}, command_statuses={:?}, turn_status={:?}",
|
||||
client.command_approval_count,
|
||||
client.command_execution_statuses,
|
||||
client.last_turn_status
|
||||
if client.command_approval_count < min_approvals {
|
||||
bail!(
|
||||
"expected at least {min_approvals} command approvals, got {}",
|
||||
client.command_approval_count
|
||||
);
|
||||
}
|
||||
let mut approvals_per_item = std::collections::BTreeMap::new();
|
||||
for item_id in &client.command_approval_item_ids {
|
||||
*approvals_per_item.entry(item_id.clone()).or_insert(0usize) += 1;
|
||||
}
|
||||
let max_approvals_for_one_item = approvals_per_item.values().copied().max().unwrap_or(0);
|
||||
if max_approvals_for_one_item < min_approvals {
|
||||
bail!(
|
||||
"expected at least {min_approvals} approvals for one command item, got max {max_approvals_for_one_item} with map {approvals_per_item:?}"
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
.await
|
||||
let last_command_status = client.command_execution_statuses.last();
|
||||
if abort_on.is_none() {
|
||||
if last_command_status != Some(&CommandExecutionStatus::Completed) {
|
||||
bail!("expected completed command execution, got {last_command_status:?}");
|
||||
}
|
||||
if client.last_turn_status != Some(TurnStatus::Completed) {
|
||||
bail!(
|
||||
"expected completed turn in all-accept flow, got {:?}",
|
||||
client.last_turn_status
|
||||
);
|
||||
}
|
||||
} else if last_command_status == Some(&CommandExecutionStatus::Completed) {
|
||||
bail!(
|
||||
"expected non-completed command execution in mixed approval/decline flow, got {last_command_status:?}"
|
||||
);
|
||||
}
|
||||
|
||||
println!(
|
||||
"[zsh-fork multi-approval summary] approvals={}, approvals_per_item={approvals_per_item:?}, command_statuses={:?}, turn_status={:?}",
|
||||
client.command_approval_count,
|
||||
client.command_execution_statuses,
|
||||
client.last_turn_status
|
||||
);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
async fn resume_message_v2(
|
||||
fn resume_message_v2(
|
||||
endpoint: &Endpoint,
|
||||
config_overrides: &[String],
|
||||
thread_id: String,
|
||||
@@ -689,7 +650,7 @@ async fn resume_message_v2(
|
||||
) -> Result<()> {
|
||||
ensure_dynamic_tools_unused(dynamic_tools, "resume-message-v2")?;
|
||||
|
||||
with_client("resume-message-v2", endpoint, config_overrides, |client| {
|
||||
with_client(endpoint, config_overrides, |client| {
|
||||
let initialize = client.initialize()?;
|
||||
println!("< initialize response: {initialize:?}");
|
||||
|
||||
@@ -713,42 +674,39 @@ async fn resume_message_v2(
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn thread_resume_follow(
|
||||
fn thread_resume_follow(
|
||||
endpoint: &Endpoint,
|
||||
config_overrides: &[String],
|
||||
thread_id: String,
|
||||
) -> Result<()> {
|
||||
with_client("thread-resume", endpoint, config_overrides, |client| {
|
||||
let initialize = client.initialize()?;
|
||||
println!("< initialize response: {initialize:?}");
|
||||
let mut client = CodexClient::connect(endpoint, config_overrides)?;
|
||||
|
||||
let resume_response = client.thread_resume(ThreadResumeParams {
|
||||
thread_id,
|
||||
..Default::default()
|
||||
})?;
|
||||
println!("< thread/resume response: {resume_response:?}");
|
||||
println!("< streaming notifications until process is terminated");
|
||||
let initialize = client.initialize()?;
|
||||
println!("< initialize response: {initialize:?}");
|
||||
|
||||
client.stream_notifications_forever()
|
||||
})
|
||||
.await
|
||||
let resume_response = client.thread_resume(ThreadResumeParams {
|
||||
thread_id,
|
||||
..Default::default()
|
||||
})?;
|
||||
println!("< thread/resume response: {resume_response:?}");
|
||||
println!("< streaming notifications until process is terminated");
|
||||
|
||||
client.stream_notifications_forever()
|
||||
}
|
||||
|
||||
async fn watch(endpoint: &Endpoint, config_overrides: &[String]) -> Result<()> {
|
||||
with_client("watch", endpoint, config_overrides, |client| {
|
||||
let initialize = client.initialize()?;
|
||||
println!("< initialize response: {initialize:?}");
|
||||
println!("< streaming inbound messages until process is terminated");
|
||||
fn watch(endpoint: &Endpoint, config_overrides: &[String]) -> Result<()> {
|
||||
let mut client = CodexClient::connect(endpoint, config_overrides)?;
|
||||
|
||||
client.stream_notifications_forever()
|
||||
})
|
||||
.await
|
||||
let initialize = client.initialize()?;
|
||||
println!("< initialize response: {initialize:?}");
|
||||
println!("< streaming inbound messages until process is terminated");
|
||||
|
||||
client.stream_notifications_forever()
|
||||
}
|
||||
|
||||
async fn trigger_cmd_approval(
|
||||
fn trigger_cmd_approval(
|
||||
endpoint: &Endpoint,
|
||||
config_overrides: &[String],
|
||||
user_message: Option<String>,
|
||||
@@ -761,21 +719,17 @@ async fn trigger_cmd_approval(
|
||||
endpoint,
|
||||
config_overrides,
|
||||
message,
|
||||
SendMessagePolicies {
|
||||
command_name: "trigger-cmd-approval",
|
||||
experimental_api: true,
|
||||
approval_policy: Some(AskForApproval::OnRequest),
|
||||
sandbox_policy: Some(SandboxPolicy::ReadOnly {
|
||||
access: ReadOnlyAccess::FullAccess,
|
||||
network_access: false,
|
||||
}),
|
||||
dynamic_tools,
|
||||
},
|
||||
true,
|
||||
Some(AskForApproval::OnRequest),
|
||||
Some(SandboxPolicy::ReadOnly {
|
||||
access: ReadOnlyAccess::FullAccess,
|
||||
network_access: false,
|
||||
}),
|
||||
dynamic_tools,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn trigger_patch_approval(
|
||||
fn trigger_patch_approval(
|
||||
endpoint: &Endpoint,
|
||||
config_overrides: &[String],
|
||||
user_message: Option<String>,
|
||||
@@ -788,21 +742,17 @@ async fn trigger_patch_approval(
|
||||
endpoint,
|
||||
config_overrides,
|
||||
message,
|
||||
SendMessagePolicies {
|
||||
command_name: "trigger-patch-approval",
|
||||
experimental_api: true,
|
||||
approval_policy: Some(AskForApproval::OnRequest),
|
||||
sandbox_policy: Some(SandboxPolicy::ReadOnly {
|
||||
access: ReadOnlyAccess::FullAccess,
|
||||
network_access: false,
|
||||
}),
|
||||
dynamic_tools,
|
||||
},
|
||||
true,
|
||||
Some(AskForApproval::OnRequest),
|
||||
Some(SandboxPolicy::ReadOnly {
|
||||
access: ReadOnlyAccess::FullAccess,
|
||||
network_access: false,
|
||||
}),
|
||||
dynamic_tools,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn no_trigger_cmd_approval(
|
||||
fn no_trigger_cmd_approval(
|
||||
endpoint: &Endpoint,
|
||||
config_overrides: &[String],
|
||||
dynamic_tools: &Option<Vec<DynamicToolSpec>>,
|
||||
@@ -812,67 +762,60 @@ async fn no_trigger_cmd_approval(
|
||||
endpoint,
|
||||
config_overrides,
|
||||
prompt.to_string(),
|
||||
SendMessagePolicies {
|
||||
command_name: "no-trigger-cmd-approval",
|
||||
experimental_api: true,
|
||||
approval_policy: None,
|
||||
sandbox_policy: None,
|
||||
dynamic_tools,
|
||||
},
|
||||
true,
|
||||
None,
|
||||
None,
|
||||
dynamic_tools,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn send_message_v2_with_policies(
|
||||
fn send_message_v2_with_policies(
|
||||
endpoint: &Endpoint,
|
||||
config_overrides: &[String],
|
||||
user_message: String,
|
||||
policies: SendMessagePolicies<'_>,
|
||||
experimental_api: bool,
|
||||
approval_policy: Option<AskForApproval>,
|
||||
sandbox_policy: Option<SandboxPolicy>,
|
||||
dynamic_tools: &Option<Vec<DynamicToolSpec>>,
|
||||
) -> Result<()> {
|
||||
with_client(
|
||||
policies.command_name,
|
||||
endpoint,
|
||||
config_overrides,
|
||||
|client| {
|
||||
let initialize = client.initialize_with_experimental_api(policies.experimental_api)?;
|
||||
println!("< initialize response: {initialize:?}");
|
||||
with_client(endpoint, config_overrides, |client| {
|
||||
let initialize = client.initialize_with_experimental_api(experimental_api)?;
|
||||
println!("< initialize response: {initialize:?}");
|
||||
|
||||
let thread_response = client.thread_start(ThreadStartParams {
|
||||
dynamic_tools: policies.dynamic_tools.clone(),
|
||||
..Default::default()
|
||||
})?;
|
||||
println!("< thread/start response: {thread_response:?}");
|
||||
let mut turn_params = TurnStartParams {
|
||||
thread_id: thread_response.thread.id.clone(),
|
||||
input: vec![V2UserInput::Text {
|
||||
text: user_message,
|
||||
// Test client sends plain text without UI element ranges.
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
turn_params.approval_policy = policies.approval_policy;
|
||||
turn_params.sandbox_policy = policies.sandbox_policy;
|
||||
let thread_response = client.thread_start(ThreadStartParams {
|
||||
dynamic_tools: dynamic_tools.clone(),
|
||||
..Default::default()
|
||||
})?;
|
||||
println!("< thread/start response: {thread_response:?}");
|
||||
let mut turn_params = TurnStartParams {
|
||||
thread_id: thread_response.thread.id.clone(),
|
||||
input: vec![V2UserInput::Text {
|
||||
text: user_message,
|
||||
// Test client sends plain text without UI element ranges.
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
turn_params.approval_policy = approval_policy;
|
||||
turn_params.sandbox_policy = sandbox_policy;
|
||||
|
||||
let turn_response = client.turn_start(turn_params)?;
|
||||
println!("< turn/start response: {turn_response:?}");
|
||||
let turn_response = client.turn_start(turn_params)?;
|
||||
println!("< turn/start response: {turn_response:?}");
|
||||
|
||||
client.stream_turn(&thread_response.thread.id, &turn_response.turn.id)?;
|
||||
client.stream_turn(&thread_response.thread.id, &turn_response.turn.id)?;
|
||||
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
.await
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
async fn send_follow_up_v2(
|
||||
fn send_follow_up_v2(
|
||||
endpoint: &Endpoint,
|
||||
config_overrides: &[String],
|
||||
first_message: String,
|
||||
follow_up_message: String,
|
||||
dynamic_tools: &Option<Vec<DynamicToolSpec>>,
|
||||
) -> Result<()> {
|
||||
with_client("send-follow-up-v2", endpoint, config_overrides, |client| {
|
||||
with_client(endpoint, config_overrides, |client| {
|
||||
let initialize = client.initialize()?;
|
||||
println!("< initialize response: {initialize:?}");
|
||||
|
||||
@@ -910,11 +853,10 @@ async fn send_follow_up_v2(
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn test_login(endpoint: &Endpoint, config_overrides: &[String]) -> Result<()> {
|
||||
with_client("test-login", endpoint, config_overrides, |client| {
|
||||
fn test_login(endpoint: &Endpoint, config_overrides: &[String]) -> Result<()> {
|
||||
with_client(endpoint, config_overrides, |client| {
|
||||
let initialize = client.initialize()?;
|
||||
println!("< initialize response: {initialize:?}");
|
||||
|
||||
@@ -941,29 +883,22 @@ async fn test_login(endpoint: &Endpoint, config_overrides: &[String]) -> Result<
|
||||
);
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_account_rate_limits(endpoint: &Endpoint, config_overrides: &[String]) -> Result<()> {
|
||||
with_client(
|
||||
"get-account-rate-limits",
|
||||
endpoint,
|
||||
config_overrides,
|
||||
|client| {
|
||||
let initialize = client.initialize()?;
|
||||
println!("< initialize response: {initialize:?}");
|
||||
fn get_account_rate_limits(endpoint: &Endpoint, config_overrides: &[String]) -> Result<()> {
|
||||
with_client(endpoint, config_overrides, |client| {
|
||||
let initialize = client.initialize()?;
|
||||
println!("< initialize response: {initialize:?}");
|
||||
|
||||
let response = client.get_account_rate_limits()?;
|
||||
println!("< account/rateLimits/read response: {response:?}");
|
||||
let response = client.get_account_rate_limits()?;
|
||||
println!("< account/rateLimits/read response: {response:?}");
|
||||
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
.await
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
async fn model_list(endpoint: &Endpoint, config_overrides: &[String]) -> Result<()> {
|
||||
with_client("model-list", endpoint, config_overrides, |client| {
|
||||
fn model_list(endpoint: &Endpoint, config_overrides: &[String]) -> Result<()> {
|
||||
with_client(endpoint, config_overrides, |client| {
|
||||
let initialize = client.initialize()?;
|
||||
println!("< initialize response: {initialize:?}");
|
||||
|
||||
@@ -972,11 +907,10 @@ async fn model_list(endpoint: &Endpoint, config_overrides: &[String]) -> Result<
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn thread_list(endpoint: &Endpoint, config_overrides: &[String], limit: u32) -> Result<()> {
|
||||
with_client("thread-list", endpoint, config_overrides, |client| {
|
||||
fn thread_list(endpoint: &Endpoint, config_overrides: &[String], limit: u32) -> Result<()> {
|
||||
with_client(endpoint, config_overrides, |client| {
|
||||
let initialize = client.initialize()?;
|
||||
println!("< initialize response: {initialize:?}");
|
||||
|
||||
@@ -994,28 +928,16 @@ async fn thread_list(endpoint: &Endpoint, config_overrides: &[String], limit: u3
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn with_client<T>(
|
||||
command_name: &'static str,
|
||||
fn with_client<T>(
|
||||
endpoint: &Endpoint,
|
||||
config_overrides: &[String],
|
||||
f: impl FnOnce(&mut CodexClient) -> Result<T>,
|
||||
) -> Result<T> {
|
||||
let tracing = TestClientTracing::initialize(config_overrides).await?;
|
||||
let command_span = info_span!(
|
||||
"app_server_test_client.command",
|
||||
otel.kind = "client",
|
||||
otel.name = command_name,
|
||||
app_server_test_client.command = command_name,
|
||||
);
|
||||
let trace_summary = command_span.in_scope(|| TraceSummary::capture(tracing.traces_enabled));
|
||||
let result = command_span.in_scope(|| {
|
||||
let mut client = CodexClient::connect(endpoint, config_overrides)?;
|
||||
f(&mut client)
|
||||
});
|
||||
print_trace_summary(&trace_summary);
|
||||
let mut client = CodexClient::connect(endpoint, config_overrides)?;
|
||||
let result = f(&mut client);
|
||||
client.print_trace_summary();
|
||||
result
|
||||
}
|
||||
|
||||
@@ -1073,6 +995,8 @@ struct CodexClient {
|
||||
command_approval_item_ids: Vec<String>,
|
||||
command_execution_statuses: Vec<CommandExecutionStatus>,
|
||||
last_turn_status: Option<TurnStatus>,
|
||||
trace_id: String,
|
||||
trace_root_span_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -1132,6 +1056,8 @@ impl CodexClient {
|
||||
command_approval_item_ids: Vec::new(),
|
||||
command_execution_statuses: Vec::new(),
|
||||
last_turn_status: None,
|
||||
trace_id: generate_trace_id(),
|
||||
trace_root_span_id: generate_parent_span_id(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1153,6 +1079,8 @@ impl CodexClient {
|
||||
command_approval_item_ids: Vec::new(),
|
||||
command_execution_statuses: Vec::new(),
|
||||
last_turn_status: None,
|
||||
trace_id: generate_trace_id(),
|
||||
trace_root_span_id: generate_parent_span_id(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1374,31 +1302,37 @@ impl CodexClient {
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
let request_span = info_span!(
|
||||
"app_server_test_client.request",
|
||||
otel.kind = "client",
|
||||
otel.name = method,
|
||||
rpc.system = "jsonrpc",
|
||||
rpc.method = method,
|
||||
rpc.request_id = ?request_id,
|
||||
);
|
||||
request_span.in_scope(|| {
|
||||
self.write_request(&request)?;
|
||||
self.wait_for_response(request_id, method)
|
||||
})
|
||||
self.write_request(&request)?;
|
||||
self.wait_for_response(request_id, method)
|
||||
}
|
||||
|
||||
fn write_request(&mut self, request: &ClientRequest) -> Result<()> {
|
||||
let request_value = serde_json::to_value(request)?;
|
||||
let mut request: JSONRPCRequest = serde_json::from_value(request_value)
|
||||
.context("client request was not a valid JSON-RPC request")?;
|
||||
request.trace = current_span_w3c_trace_context();
|
||||
let request = self.jsonrpc_request_with_trace(request)?;
|
||||
let request_json = serde_json::to_string(&request)?;
|
||||
let request_pretty = serde_json::to_string_pretty(&request)?;
|
||||
print_multiline_with_prefix("> ", &request_pretty);
|
||||
self.write_payload(&request_json)
|
||||
}
|
||||
|
||||
fn jsonrpc_request_with_trace(&self, request: &ClientRequest) -> Result<JSONRPCRequest> {
|
||||
let request_value = serde_json::to_value(request)?;
|
||||
let mut request: JSONRPCRequest = serde_json::from_value(request_value)
|
||||
.context("client request was not a valid JSON-RPC request")?;
|
||||
request.trace = Some(W3cTraceContext {
|
||||
traceparent: Some(format!(
|
||||
"00-{}-{}-01",
|
||||
self.trace_id, self.trace_root_span_id
|
||||
)),
|
||||
tracestate: None,
|
||||
});
|
||||
Ok(request)
|
||||
}
|
||||
|
||||
fn print_trace_summary(&self) {
|
||||
println!("\n[Datadog trace]");
|
||||
println!("go/trace/{}\n", self.trace_id);
|
||||
}
|
||||
|
||||
fn wait_for_response<T>(&mut self, request_id: RequestId, method: &str) -> Result<T>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
@@ -1664,91 +1598,21 @@ impl CodexClient {
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_trace_id() -> String {
|
||||
Uuid::new_v4().simple().to_string()
|
||||
}
|
||||
|
||||
fn generate_parent_span_id() -> String {
|
||||
let uuid = Uuid::new_v4().simple().to_string();
|
||||
uuid[..16].to_string()
|
||||
}
|
||||
|
||||
fn print_multiline_with_prefix(prefix: &str, payload: &str) {
|
||||
for line in payload.lines() {
|
||||
println!("{prefix}{line}");
|
||||
}
|
||||
}
|
||||
|
||||
struct TestClientTracing {
|
||||
_otel_provider: Option<OtelProvider>,
|
||||
traces_enabled: bool,
|
||||
}
|
||||
|
||||
impl TestClientTracing {
|
||||
async fn initialize(config_overrides: &[String]) -> Result<Self> {
|
||||
let cli_kv_overrides = CliConfigOverrides {
|
||||
raw_overrides: config_overrides.to_vec(),
|
||||
}
|
||||
.parse_overrides()
|
||||
.map_err(|e| anyhow::anyhow!("error parsing -c overrides: {e}"))?;
|
||||
let config = Config::load_with_cli_overrides(cli_kv_overrides)
|
||||
.await
|
||||
.context("error loading config")?;
|
||||
let otel_provider = codex_core::otel_init::build_provider(
|
||||
&config,
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
Some(OTEL_SERVICE_NAME),
|
||||
DEFAULT_ANALYTICS_ENABLED,
|
||||
)
|
||||
.map_err(|e| anyhow::anyhow!("error loading otel config: {e}"))?;
|
||||
let traces_enabled = otel_provider
|
||||
.as_ref()
|
||||
.and_then(|provider| provider.tracer_provider.as_ref())
|
||||
.is_some();
|
||||
if let Some(provider) = otel_provider.as_ref()
|
||||
&& traces_enabled
|
||||
{
|
||||
let _ = tracing_subscriber::registry()
|
||||
.with(provider.tracing_layer())
|
||||
.try_init();
|
||||
}
|
||||
Ok(Self {
|
||||
traces_enabled,
|
||||
_otel_provider: otel_provider,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
enum TraceSummary {
|
||||
Enabled { url: String },
|
||||
Disabled,
|
||||
}
|
||||
|
||||
impl TraceSummary {
|
||||
fn capture(traces_enabled: bool) -> Self {
|
||||
if !traces_enabled {
|
||||
return Self::Disabled;
|
||||
}
|
||||
current_span_w3c_trace_context()
|
||||
.as_ref()
|
||||
.and_then(trace_url_from_context)
|
||||
.map_or(Self::Disabled, |url| Self::Enabled { url })
|
||||
}
|
||||
}
|
||||
|
||||
fn trace_url_from_context(trace: &W3cTraceContext) -> Option<String> {
|
||||
let traceparent = trace.traceparent.as_deref()?;
|
||||
let mut parts = traceparent.split('-');
|
||||
match (parts.next(), parts.next(), parts.next(), parts.next()) {
|
||||
(Some(_version), Some(trace_id), Some(_span_id), Some(_trace_flags))
|
||||
if trace_id.len() == 32 =>
|
||||
{
|
||||
Some(format!("go/trace/{trace_id}"))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn print_trace_summary(trace_summary: &TraceSummary) {
|
||||
println!("\n[Datadog trace]");
|
||||
match trace_summary {
|
||||
TraceSummary::Enabled { url } => println!("{url}\n"),
|
||||
TraceSummary::Disabled => println!("{TRACE_DISABLED_MESSAGE}\n"),
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for CodexClient {
|
||||
fn drop(&mut self) {
|
||||
let ClientTransport::Stdio { child, stdin, .. } = &mut self.transport else {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use anyhow::Result;
|
||||
use tokio::runtime::Builder;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let runtime = Builder::new_current_thread().enable_all().build()?;
|
||||
runtime.block_on(codex_app_server_test_client::run())
|
||||
codex_app_server_test_client::run()
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
] }
|
||||
|
||||
@@ -153,12 +153,11 @@ Example with notification opt-out:
|
||||
- `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 `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.
|
||||
- `mcpServerStatus/list` — enumerate configured MCP servers with their tools, resources, resource templates, and auth status; supports cursor+limit pagination.
|
||||
- `windowsSandbox/setupStart` — start Windows sandbox setup for the selected mode (`elevated` or `unelevated`); accepts an optional `cwd` to target setup for a specific workspace, returns `{ started: true }` immediately, and later emits `windowsSandbox/setupCompleted`.
|
||||
- `windowsSandbox/setupStart` — start Windows sandbox setup for the selected mode (`elevated` or `unelevated`); returns `{ started: true }` immediately and later emits `windowsSandbox/setupCompleted`.
|
||||
- `feedback/upload` — submit a feedback report (classification + optional reason/logs, conversation_id, and optional `extraLogFiles` attachments array); returns the tracking thread id.
|
||||
- `command/exec` — run a single command under the server sandbox without starting a thread/turn (handy for utilities and validation).
|
||||
- `config/read` — fetch the effective config on disk after resolving config layering.
|
||||
@@ -764,20 +763,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`.
|
||||
|
||||
@@ -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,38 +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 = {
|
||||
let state = thread_state.lock().await;
|
||||
state.active_turn_snapshot().map(|turn| turn.id)
|
||||
};
|
||||
let params = McpServerElicitationRequestParams {
|
||||
thread_id: conversation_id.to_string(),
|
||||
turn_id,
|
||||
server_name: request.server_name.clone(),
|
||||
request: request.request.into(),
|
||||
};
|
||||
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;
|
||||
@@ -2024,68 +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,
|
||||
})
|
||||
.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,
|
||||
}
|
||||
}),
|
||||
Ok(Err(err)) if is_turn_transition_server_request_error(&err) => {
|
||||
McpServerElicitationRequestResponse {
|
||||
action: McpServerElicitationAction::Cancel,
|
||||
content: None,
|
||||
}
|
||||
}
|
||||
Ok(Err(err)) => {
|
||||
error!("request failed with client error: {err:?}");
|
||||
McpServerElicitationRequestResponse {
|
||||
action: McpServerElicitationAction::Decline,
|
||||
content: None,
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!("request failed: {err:?}");
|
||||
McpServerElicitationRequestResponse {
|
||||
action: McpServerElicitationAction::Decline,
|
||||
content: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const REVIEW_FALLBACK_MESSAGE: &str = "Reviewer failed to output a response.";
|
||||
|
||||
fn render_review_output_text(output: &ReviewOutputEvent) -> String {
|
||||
@@ -2431,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;
|
||||
@@ -2476,25 +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,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collab_resume_begin_maps_to_item_started_resume_agent() {
|
||||
let event = CollabResumeBeginEvent {
|
||||
|
||||
@@ -77,8 +77,6 @@ use codex_app_server_protocol::MockExperimentalMethodParams;
|
||||
use codex_app_server_protocol::MockExperimentalMethodResponse;
|
||||
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::ProductSurface as ApiProductSurface;
|
||||
use codex_app_server_protocol::RequestId;
|
||||
use codex_app_server_protocol::ReviewDelivery as ApiReviewDelivery;
|
||||
@@ -198,8 +196,6 @@ 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::PluginInstallError as CorePluginInstallError;
|
||||
use codex_core::plugins::PluginInstallRequest;
|
||||
use codex_core::read_head_for_summary;
|
||||
use codex_core::read_session_meta_line;
|
||||
use codex_core::rollout_date_parts;
|
||||
@@ -662,10 +658,6 @@ impl CodexMessageProcessor {
|
||||
self.skills_config_write(to_connection_request_id(request_id), params)
|
||||
.await;
|
||||
}
|
||||
ClientRequest::PluginInstall { request_id, params } => {
|
||||
self.plugin_install(to_connection_request_id(request_id), params)
|
||||
.await;
|
||||
}
|
||||
ClientRequest::TurnStart { request_id, params } => {
|
||||
self.turn_start(
|
||||
to_connection_request_id(request_id),
|
||||
@@ -1514,9 +1506,19 @@ impl CodexMessageProcessor {
|
||||
};
|
||||
|
||||
let requested_policy = params.sandbox_policy.map(|policy| policy.to_core());
|
||||
let effective_policy = match requested_policy {
|
||||
let (
|
||||
effective_policy,
|
||||
effective_file_system_sandbox_policy,
|
||||
effective_network_sandbox_policy,
|
||||
) = match requested_policy {
|
||||
Some(policy) => match self.config.permissions.sandbox_policy.can_set(&policy) {
|
||||
Ok(()) => policy,
|
||||
Ok(()) => {
|
||||
let file_system_sandbox_policy =
|
||||
codex_protocol::protocol::FileSystemSandboxPolicy::from(&policy);
|
||||
let network_sandbox_policy =
|
||||
codex_protocol::protocol::NetworkSandboxPolicy::from(&policy);
|
||||
(policy, file_system_sandbox_policy, network_sandbox_policy)
|
||||
}
|
||||
Err(err) => {
|
||||
let error = JSONRPCErrorError {
|
||||
code: INVALID_REQUEST_ERROR_CODE,
|
||||
@@ -1527,7 +1529,11 @@ impl CodexMessageProcessor {
|
||||
return;
|
||||
}
|
||||
},
|
||||
None => self.config.permissions.sandbox_policy.get().clone(),
|
||||
None => (
|
||||
self.config.permissions.sandbox_policy.get().clone(),
|
||||
self.config.permissions.file_system_sandbox_policy.clone(),
|
||||
self.config.permissions.network_sandbox_policy,
|
||||
),
|
||||
};
|
||||
|
||||
let codex_linux_sandbox_exe = self.arg0_paths.codex_linux_sandbox_exe.clone();
|
||||
@@ -1542,6 +1548,8 @@ impl CodexMessageProcessor {
|
||||
match codex_core::exec::process_exec_tool_call(
|
||||
exec_params,
|
||||
&effective_policy,
|
||||
&effective_file_system_sandbox_policy,
|
||||
effective_network_sandbox_policy,
|
||||
sandbox_cwd.as_path(),
|
||||
&codex_linux_sandbox_exe,
|
||||
use_linux_sandbox_bwrap,
|
||||
@@ -2701,13 +2709,7 @@ impl CodexMessageProcessor {
|
||||
}
|
||||
};
|
||||
|
||||
let loaded_thread = self.thread_manager.get_thread(thread_uuid).await.ok();
|
||||
let loaded_thread_state_db = loaded_thread.as_ref().and_then(|thread| thread.state_db());
|
||||
let db_summary = if let Some(state_db_ctx) = loaded_thread_state_db.as_ref() {
|
||||
read_summary_from_state_db_context_by_thread_id(Some(state_db_ctx), thread_uuid).await
|
||||
} else {
|
||||
read_summary_from_state_db_by_thread_id(&self.config, thread_uuid).await
|
||||
};
|
||||
let db_summary = read_summary_from_state_db_by_thread_id(&self.config, thread_uuid).await;
|
||||
let mut rollout_path = db_summary.as_ref().map(|summary| summary.path.clone());
|
||||
if rollout_path.is_none() || include_turns {
|
||||
rollout_path =
|
||||
@@ -2761,7 +2763,7 @@ impl CodexMessageProcessor {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let Some(thread) = loaded_thread else {
|
||||
let Ok(thread) = self.thread_manager.get_thread(thread_uuid).await else {
|
||||
self.send_invalid_request_error(
|
||||
request_id,
|
||||
format!("thread not loaded: {thread_uuid}"),
|
||||
@@ -2966,7 +2968,6 @@ impl CodexMessageProcessor {
|
||||
};
|
||||
|
||||
let fallback_model_provider = config.model_provider_id.clone();
|
||||
let response_history = thread_history.clone();
|
||||
|
||||
match self
|
||||
.thread_manager
|
||||
@@ -2980,8 +2981,8 @@ impl CodexMessageProcessor {
|
||||
{
|
||||
Ok(NewThread {
|
||||
thread_id,
|
||||
thread,
|
||||
session_configured,
|
||||
..
|
||||
}) => {
|
||||
let SessionConfiguredEvent { rollout_path, .. } = session_configured;
|
||||
let Some(rollout_path) = rollout_path else {
|
||||
@@ -3007,11 +3008,9 @@ impl CodexMessageProcessor {
|
||||
);
|
||||
|
||||
let Some(mut thread) = self
|
||||
.load_thread_from_resume_source_or_send_internal(
|
||||
.load_thread_from_rollout_or_send_internal(
|
||||
request_id.clone(),
|
||||
thread_id,
|
||||
thread.as_ref(),
|
||||
&response_history,
|
||||
rollout_path.as_path(),
|
||||
fallback_model_provider.as_str(),
|
||||
)
|
||||
@@ -3166,20 +3165,6 @@ impl CodexMessageProcessor {
|
||||
mismatch_details.join("; ")
|
||||
);
|
||||
}
|
||||
let thread_summary = match load_thread_summary_for_rollout(
|
||||
&self.config,
|
||||
existing_thread_id,
|
||||
rollout_path.as_path(),
|
||||
config_snapshot.model_provider_id.as_str(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(thread) => thread,
|
||||
Err(message) => {
|
||||
self.send_internal_error(request_id, message).await;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
let listener_command_tx = {
|
||||
let thread_state = thread_state.lock().await;
|
||||
@@ -3200,9 +3185,8 @@ impl CodexMessageProcessor {
|
||||
let command = crate::thread_state::ThreadListenerCommand::SendThreadResumeResponse(
|
||||
Box::new(crate::thread_state::PendingThreadResumeRequest {
|
||||
request_id: request_id.clone(),
|
||||
rollout_path: rollout_path.clone(),
|
||||
rollout_path,
|
||||
config_snapshot,
|
||||
thread_summary,
|
||||
}),
|
||||
);
|
||||
if listener_command_tx.send(command).is_err() {
|
||||
@@ -3300,61 +3284,45 @@ impl CodexMessageProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
async fn load_thread_from_resume_source_or_send_internal(
|
||||
async fn load_thread_from_rollout_or_send_internal(
|
||||
&self,
|
||||
request_id: ConnectionRequestId,
|
||||
thread_id: ThreadId,
|
||||
thread: &CodexThread,
|
||||
thread_history: &InitialHistory,
|
||||
rollout_path: &Path,
|
||||
fallback_provider: &str,
|
||||
) -> Option<Thread> {
|
||||
let thread = match thread_history {
|
||||
InitialHistory::Resumed(resumed) => {
|
||||
load_thread_summary_for_rollout(
|
||||
&self.config,
|
||||
resumed.conversation_id,
|
||||
resumed.rollout_path.as_path(),
|
||||
fallback_provider,
|
||||
let mut thread = match read_summary_from_rollout(rollout_path, fallback_provider).await {
|
||||
Ok(summary) => summary_to_thread(summary),
|
||||
Err(err) => {
|
||||
self.send_internal_error(
|
||||
request_id,
|
||||
format!(
|
||||
"failed to load rollout `{}` for thread {thread_id}: {err}",
|
||||
rollout_path.display()
|
||||
),
|
||||
)
|
||||
.await
|
||||
}
|
||||
InitialHistory::Forked(items) => {
|
||||
let config_snapshot = thread.config_snapshot().await;
|
||||
let mut thread = build_thread_from_snapshot(
|
||||
thread_id,
|
||||
&config_snapshot,
|
||||
Some(rollout_path.into()),
|
||||
);
|
||||
thread.preview = preview_from_rollout_items(items);
|
||||
Ok(thread)
|
||||
}
|
||||
InitialHistory::New => Err(format!(
|
||||
"failed to build resume response for thread {thread_id}: initial history missing"
|
||||
)),
|
||||
};
|
||||
let mut thread = match thread {
|
||||
Ok(thread) => thread,
|
||||
Err(message) => {
|
||||
self.send_internal_error(request_id, message).await;
|
||||
.await;
|
||||
return None;
|
||||
}
|
||||
};
|
||||
thread.id = thread_id.to_string();
|
||||
thread.path = Some(rollout_path.to_path_buf());
|
||||
let history_items = thread_history.get_rollout_items();
|
||||
if let Err(message) = populate_resume_turns(
|
||||
&mut thread,
|
||||
ResumeTurnSource::HistoryItems(&history_items),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
{
|
||||
self.send_internal_error(request_id, message).await;
|
||||
return None;
|
||||
match read_rollout_items_from_rollout(rollout_path).await {
|
||||
Ok(items) => {
|
||||
thread.turns = build_turns_from_rollout_items(&items);
|
||||
self.attach_thread_name(thread_id, &mut thread).await;
|
||||
Some(thread)
|
||||
}
|
||||
Err(err) => {
|
||||
self.send_internal_error(
|
||||
request_id,
|
||||
format!(
|
||||
"failed to load rollout `{}` for thread {thread_id}: {err}",
|
||||
rollout_path.display()
|
||||
),
|
||||
)
|
||||
.await;
|
||||
None
|
||||
}
|
||||
}
|
||||
self.attach_thread_name(thread_id, &mut thread).await;
|
||||
Some(thread)
|
||||
}
|
||||
|
||||
async fn attach_thread_name(&self, thread_id: ThreadId, thread: &mut Thread) {
|
||||
@@ -5032,56 +5000,6 @@ impl CodexMessageProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
async fn plugin_install(&self, request_id: ConnectionRequestId, params: PluginInstallParams) {
|
||||
let PluginInstallParams {
|
||||
marketplace_name,
|
||||
plugin_name,
|
||||
cwd,
|
||||
} = params;
|
||||
|
||||
let plugins_manager = self.thread_manager.plugins_manager();
|
||||
let request = PluginInstallRequest {
|
||||
plugin_name,
|
||||
marketplace_name,
|
||||
cwd: cwd.unwrap_or_else(|| self.config.cwd.clone()),
|
||||
};
|
||||
|
||||
match plugins_manager.install_plugin(request).await {
|
||||
Ok(_) => {
|
||||
plugins_manager.clear_cache();
|
||||
self.thread_manager.skills_manager().clear_cache();
|
||||
self.outgoing
|
||||
.send_response(request_id, PluginInstallResponse {})
|
||||
.await;
|
||||
}
|
||||
Err(err) => {
|
||||
if err.is_invalid_request() {
|
||||
self.send_invalid_request_error(request_id, err.to_string())
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
match err {
|
||||
CorePluginInstallError::Config(err) => {
|
||||
self.send_internal_error(
|
||||
request_id,
|
||||
format!("failed to persist installed plugin config: {err}"),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
CorePluginInstallError::Join(err) => {
|
||||
self.send_internal_error(
|
||||
request_id,
|
||||
format!("failed to install plugin: {err}"),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
CorePluginInstallError::Marketplace(_) | CorePluginInstallError::Store(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn turn_start(
|
||||
&self,
|
||||
request_id: ConnectionRequestId,
|
||||
@@ -6217,39 +6135,21 @@ impl CodexMessageProcessor {
|
||||
WindowsSandboxSetupMode::Unelevated => CoreWindowsSandboxSetupMode::Unelevated,
|
||||
};
|
||||
let config = Arc::clone(&self.config);
|
||||
let cli_overrides = self.cli_overrides.clone();
|
||||
let cloud_requirements = self.current_cloud_requirements();
|
||||
let command_cwd = params.cwd.unwrap_or_else(|| config.cwd.clone());
|
||||
let outgoing = Arc::clone(&self.outgoing);
|
||||
let connection_id = request_id.connection_id;
|
||||
|
||||
tokio::spawn(async move {
|
||||
let derived_config = derive_config_for_cwd(
|
||||
&cli_overrides,
|
||||
None,
|
||||
ConfigOverrides {
|
||||
cwd: Some(command_cwd.clone()),
|
||||
..Default::default()
|
||||
},
|
||||
Some(command_cwd.clone()),
|
||||
&cloud_requirements,
|
||||
)
|
||||
.await;
|
||||
let setup_result = match derived_config {
|
||||
Ok(config) => {
|
||||
let setup_request = WindowsSandboxSetupRequest {
|
||||
mode,
|
||||
policy: config.permissions.sandbox_policy.get().clone(),
|
||||
policy_cwd: config.cwd.clone(),
|
||||
command_cwd,
|
||||
env_map: std::env::vars().collect(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
active_profile: config.active_profile.clone(),
|
||||
};
|
||||
codex_core::windows_sandbox::run_windows_sandbox_setup(setup_request).await
|
||||
}
|
||||
Err(err) => Err(err.into()),
|
||||
let setup_request = WindowsSandboxSetupRequest {
|
||||
mode,
|
||||
policy: config.permissions.sandbox_policy.get().clone(),
|
||||
policy_cwd: config.cwd.clone(),
|
||||
command_cwd: config.cwd.clone(),
|
||||
env_map: std::env::vars().collect(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
active_profile: config.active_profile.clone(),
|
||||
};
|
||||
let setup_result =
|
||||
codex_core::windows_sandbox::run_windows_sandbox_setup(setup_request).await;
|
||||
let notification = WindowsSandboxSetupCompletedNotification {
|
||||
mode: match mode {
|
||||
CoreWindowsSandboxSetupMode::Elevated => WindowsSandboxSetupMode::Elevated,
|
||||
@@ -6340,26 +6240,29 @@ async fn handle_pending_thread_resume_request(
|
||||
|
||||
let request_id = pending.request_id;
|
||||
let connection_id = request_id.connection_id;
|
||||
let mut thread = pending.thread_summary;
|
||||
if let Err(message) = populate_resume_turns(
|
||||
&mut thread,
|
||||
ResumeTurnSource::RolloutPath(pending.rollout_path.as_path()),
|
||||
let mut thread = match load_thread_for_running_resume_response(
|
||||
conversation_id,
|
||||
pending.rollout_path.as_path(),
|
||||
pending.config_snapshot.model_provider_id.as_str(),
|
||||
active_turn.as_ref(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
outgoing
|
||||
.send_error(
|
||||
request_id,
|
||||
JSONRPCErrorError {
|
||||
code: INTERNAL_ERROR_CODE,
|
||||
message,
|
||||
data: None,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
Ok(thread) => thread,
|
||||
Err(message) => {
|
||||
outgoing
|
||||
.send_error(
|
||||
request_id,
|
||||
JSONRPCErrorError {
|
||||
code: INTERNAL_ERROR_CODE,
|
||||
message,
|
||||
data: None,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
has_in_progress_turn = has_in_progress_turn
|
||||
|| thread
|
||||
@@ -6409,38 +6312,6 @@ async fn handle_pending_thread_resume_request(
|
||||
.await;
|
||||
}
|
||||
|
||||
enum ResumeTurnSource<'a> {
|
||||
RolloutPath(&'a Path),
|
||||
HistoryItems(&'a [RolloutItem]),
|
||||
}
|
||||
|
||||
async fn populate_resume_turns(
|
||||
thread: &mut Thread,
|
||||
turn_source: ResumeTurnSource<'_>,
|
||||
active_turn: Option<&Turn>,
|
||||
) -> std::result::Result<(), String> {
|
||||
let mut turns = match turn_source {
|
||||
ResumeTurnSource::RolloutPath(rollout_path) => {
|
||||
read_rollout_items_from_rollout(rollout_path)
|
||||
.await
|
||||
.map(|items| build_turns_from_rollout_items(&items))
|
||||
.map_err(|err| {
|
||||
format!(
|
||||
"failed to load rollout `{}` for thread {}: {err}",
|
||||
rollout_path.display(),
|
||||
thread.id
|
||||
)
|
||||
})?
|
||||
}
|
||||
ResumeTurnSource::HistoryItems(items) => build_turns_from_rollout_items(items),
|
||||
};
|
||||
if let Some(active_turn) = active_turn {
|
||||
merge_turn_history_with_active_turn(&mut turns, active_turn.clone());
|
||||
}
|
||||
thread.turns = turns;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn resolve_pending_server_request(
|
||||
conversation_id: ThreadId,
|
||||
thread_state_manager: &ThreadStateManager,
|
||||
@@ -6466,6 +6337,38 @@ async fn resolve_pending_server_request(
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn load_thread_for_running_resume_response(
|
||||
conversation_id: ThreadId,
|
||||
rollout_path: &Path,
|
||||
fallback_provider: &str,
|
||||
active_turn: Option<&Turn>,
|
||||
) -> std::result::Result<Thread, String> {
|
||||
let mut thread = read_summary_from_rollout(rollout_path, fallback_provider)
|
||||
.await
|
||||
.map(summary_to_thread)
|
||||
.map_err(|err| {
|
||||
format!(
|
||||
"failed to load rollout `{}` for thread {conversation_id}: {err}",
|
||||
rollout_path.display()
|
||||
)
|
||||
})?;
|
||||
|
||||
let mut turns = read_rollout_items_from_rollout(rollout_path)
|
||||
.await
|
||||
.map(|items| build_turns_from_rollout_items(&items))
|
||||
.map_err(|err| {
|
||||
format!(
|
||||
"failed to load rollout `{}` for thread {conversation_id}: {err}",
|
||||
rollout_path.display()
|
||||
)
|
||||
})?;
|
||||
if let Some(active_turn) = active_turn {
|
||||
merge_turn_history_with_active_turn(&mut turns, active_turn.clone());
|
||||
}
|
||||
thread.turns = turns;
|
||||
Ok(thread)
|
||||
}
|
||||
|
||||
fn merge_turn_history_with_active_turn(turns: &mut Vec<Turn>, active_turn: Turn) {
|
||||
turns.retain(|turn| turn.id != active_turn.id);
|
||||
turns.push(active_turn);
|
||||
@@ -6493,14 +6396,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 {
|
||||
@@ -7071,48 +6966,6 @@ fn map_git_info(git_info: &CoreGitInfo) -> ConversationGitInfo {
|
||||
}
|
||||
}
|
||||
|
||||
async fn load_thread_summary_for_rollout(
|
||||
config: &Config,
|
||||
thread_id: ThreadId,
|
||||
rollout_path: &Path,
|
||||
fallback_provider: &str,
|
||||
) -> std::result::Result<Thread, String> {
|
||||
let mut thread = read_summary_from_rollout(rollout_path, fallback_provider)
|
||||
.await
|
||||
.map(summary_to_thread)
|
||||
.map_err(|err| {
|
||||
format!(
|
||||
"failed to load rollout `{}` for thread {thread_id}: {err}",
|
||||
rollout_path.display()
|
||||
)
|
||||
})?;
|
||||
if let Some(summary) = read_summary_from_state_db_by_thread_id(config, thread_id).await {
|
||||
merge_mutable_thread_metadata(&mut thread, summary_to_thread(summary));
|
||||
}
|
||||
Ok(thread)
|
||||
}
|
||||
|
||||
fn merge_mutable_thread_metadata(thread: &mut Thread, persisted_thread: Thread) {
|
||||
thread.git_info = persisted_thread.git_info;
|
||||
}
|
||||
|
||||
fn preview_from_rollout_items(items: &[RolloutItem]) -> String {
|
||||
items
|
||||
.iter()
|
||||
.find_map(|item| match item {
|
||||
RolloutItem::ResponseItem(item) => match codex_core::parse_turn_item(item) {
|
||||
Some(codex_protocol::items::TurnItem::UserMessage(user)) => Some(user.message()),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
})
|
||||
.map(|preview| match preview.find(USER_MESSAGE_BEGIN) {
|
||||
Some(idx) => preview[idx + USER_MESSAGE_BEGIN.len()..].trim().to_string(),
|
||||
None => preview,
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn with_thread_spawn_agent_metadata(
|
||||
source: codex_protocol::protocol::SessionSource,
|
||||
agent_nickname: Option<String>,
|
||||
@@ -7267,43 +7120,6 @@ mod tests {
|
||||
validate_dynamic_tools(&tools).expect("valid schema");
|
||||
}
|
||||
|
||||
#[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,
|
||||
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")?;
|
||||
|
||||
@@ -124,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
|
||||
@@ -152,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;
|
||||
@@ -161,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,
|
||||
);
|
||||
}
|
||||
@@ -174,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;
|
||||
@@ -186,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);
|
||||
}
|
||||
@@ -378,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| {
|
||||
@@ -632,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");
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ pub(crate) struct PendingThreadResumeRequest {
|
||||
pub(crate) request_id: ConnectionRequestId,
|
||||
pub(crate) rollout_path: PathBuf,
|
||||
pub(crate) config_snapshot: ThreadConfigSnapshot,
|
||||
pub(crate) thread_summary: codex_app_server_protocol::Thread,
|
||||
}
|
||||
|
||||
// ThreadListenerCommand is used to perform operations in the context of the thread listener, for serialization purposes.
|
||||
|
||||
@@ -36,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);
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -1,477 +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::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 = 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 {
|
||||
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,
|
||||
})),
|
||||
})?,
|
||||
)
|
||||
.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,7 +11,6 @@ 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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -23,8 +23,6 @@ use codex_app_server_protocol::RequestId;
|
||||
use codex_app_server_protocol::ServerRequest;
|
||||
use codex_app_server_protocol::SessionSource;
|
||||
use codex_app_server_protocol::ThreadItem;
|
||||
use codex_app_server_protocol::ThreadMetadataGitInfoUpdateParams;
|
||||
use codex_app_server_protocol::ThreadMetadataUpdateParams;
|
||||
use codex_app_server_protocol::ThreadResumeParams;
|
||||
use codex_app_server_protocol::ThreadResumeResponse;
|
||||
use codex_app_server_protocol::ThreadStartParams;
|
||||
@@ -34,27 +32,19 @@ use codex_app_server_protocol::TurnStartParams;
|
||||
use codex_app_server_protocol::TurnStartResponse;
|
||||
use codex_app_server_protocol::TurnStatus;
|
||||
use codex_app_server_protocol::UserInput;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::config_types::Personality;
|
||||
use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::protocol::SessionMeta;
|
||||
use codex_protocol::protocol::SessionMetaLine;
|
||||
use codex_protocol::protocol::SessionSource as RolloutSessionSource;
|
||||
use codex_protocol::user_input::ByteRange;
|
||||
use codex_protocol::user_input::TextElement;
|
||||
use codex_state::StateRuntime;
|
||||
use core_test_support::responses;
|
||||
use core_test_support::skip_if_no_network;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
use std::fs::FileTimes;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use tempfile::TempDir;
|
||||
use tokio::time::timeout;
|
||||
use uuid::Uuid;
|
||||
|
||||
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.";
|
||||
@@ -180,198 +170,6 @@ async fn thread_resume_returns_rollout_history() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_resume_prefers_persisted_git_metadata_for_local_threads() -> Result<()> {
|
||||
let server = create_mock_responses_server_repeating_assistant("Done").await;
|
||||
let codex_home = TempDir::new()?;
|
||||
let config_toml = codex_home.path().join("config.toml");
|
||||
std::fs::write(
|
||||
&config_toml,
|
||||
format!(
|
||||
r#"
|
||||
model = "gpt-5.2-codex"
|
||||
approval_policy = "never"
|
||||
sandbox_mode = "read-only"
|
||||
|
||||
model_provider = "mock_provider"
|
||||
|
||||
[features]
|
||||
personality = true
|
||||
sqlite = true
|
||||
|
||||
[model_providers.mock_provider]
|
||||
name = "Mock provider for test"
|
||||
base_url = "{}/v1"
|
||||
wire_api = "responses"
|
||||
request_max_retries = 0
|
||||
stream_max_retries = 0
|
||||
"#,
|
||||
server.uri()
|
||||
),
|
||||
)?;
|
||||
|
||||
let repo_path = codex_home.path().join("repo");
|
||||
std::fs::create_dir_all(&repo_path)?;
|
||||
assert!(
|
||||
Command::new("git")
|
||||
.args(["init"])
|
||||
.arg(&repo_path)
|
||||
.status()?
|
||||
.success()
|
||||
);
|
||||
assert!(
|
||||
Command::new("git")
|
||||
.current_dir(&repo_path)
|
||||
.args(["checkout", "-B", "master"])
|
||||
.status()?
|
||||
.success()
|
||||
);
|
||||
assert!(
|
||||
Command::new("git")
|
||||
.current_dir(&repo_path)
|
||||
.args(["config", "user.name", "Test User"])
|
||||
.status()?
|
||||
.success()
|
||||
);
|
||||
assert!(
|
||||
Command::new("git")
|
||||
.current_dir(&repo_path)
|
||||
.args(["config", "user.email", "test@example.com"])
|
||||
.status()?
|
||||
.success()
|
||||
);
|
||||
std::fs::write(repo_path.join("README.md"), "test\n")?;
|
||||
assert!(
|
||||
Command::new("git")
|
||||
.current_dir(&repo_path)
|
||||
.args(["add", "README.md"])
|
||||
.status()?
|
||||
.success()
|
||||
);
|
||||
assert!(
|
||||
Command::new("git")
|
||||
.current_dir(&repo_path)
|
||||
.args(["commit", "-m", "initial"])
|
||||
.status()?
|
||||
.success()
|
||||
);
|
||||
let head_branch = Command::new("git")
|
||||
.current_dir(&repo_path)
|
||||
.args(["branch", "--show-current"])
|
||||
.output()?;
|
||||
assert_eq!(
|
||||
String::from_utf8(head_branch.stdout)?.trim(),
|
||||
"master",
|
||||
"test repo should stay on master to verify resume ignores live HEAD"
|
||||
);
|
||||
|
||||
let thread_id = Uuid::new_v4().to_string();
|
||||
let conversation_id = ThreadId::from_string(&thread_id)?;
|
||||
let rollout_path = rollout_path(codex_home.path(), "2025-01-05T12-00-00", &thread_id);
|
||||
let rollout_dir = rollout_path.parent().expect("rollout parent directory");
|
||||
std::fs::create_dir_all(rollout_dir)?;
|
||||
let session_meta = SessionMeta {
|
||||
id: conversation_id,
|
||||
forked_from_id: None,
|
||||
timestamp: "2025-01-05T12:00:00Z".to_string(),
|
||||
cwd: repo_path.clone(),
|
||||
originator: "codex".to_string(),
|
||||
cli_version: "0.0.0".to_string(),
|
||||
source: RolloutSessionSource::Cli,
|
||||
agent_nickname: None,
|
||||
agent_role: None,
|
||||
model_provider: Some("mock_provider".to_string()),
|
||||
base_instructions: None,
|
||||
dynamic_tools: None,
|
||||
memory_mode: None,
|
||||
};
|
||||
std::fs::write(
|
||||
&rollout_path,
|
||||
[
|
||||
json!({
|
||||
"timestamp": "2025-01-05T12:00:00Z",
|
||||
"type": "session_meta",
|
||||
"payload": serde_json::to_value(SessionMetaLine {
|
||||
meta: session_meta,
|
||||
git: None,
|
||||
})?,
|
||||
})
|
||||
.to_string(),
|
||||
json!({
|
||||
"timestamp": "2025-01-05T12:00:00Z",
|
||||
"type": "response_item",
|
||||
"payload": {
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [{"type": "input_text", "text": "Saved user message"}]
|
||||
}
|
||||
})
|
||||
.to_string(),
|
||||
json!({
|
||||
"timestamp": "2025-01-05T12:00:00Z",
|
||||
"type": "event_msg",
|
||||
"payload": {
|
||||
"type": "user_message",
|
||||
"message": "Saved user message",
|
||||
"kind": "plain"
|
||||
}
|
||||
})
|
||||
.to_string(),
|
||||
]
|
||||
.join("\n")
|
||||
+ "\n",
|
||||
)?;
|
||||
let state_db = StateRuntime::init(
|
||||
codex_home.path().to_path_buf(),
|
||||
"mock_provider".into(),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
state_db.mark_backfill_complete(None).await?;
|
||||
|
||||
let mut mcp = McpProcess::new(codex_home.path()).await?;
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let update_id = mcp
|
||||
.send_thread_metadata_update_request(ThreadMetadataUpdateParams {
|
||||
thread_id: thread_id.clone(),
|
||||
git_info: Some(ThreadMetadataGitInfoUpdateParams {
|
||||
sha: None,
|
||||
branch: Some(Some("feature/pr-branch".to_string())),
|
||||
origin_url: None,
|
||||
}),
|
||||
})
|
||||
.await?;
|
||||
timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(update_id)),
|
||||
)
|
||||
.await??;
|
||||
|
||||
let resume_id = mcp
|
||||
.send_thread_resume_request(ThreadResumeParams {
|
||||
thread_id,
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
let resume_resp: JSONRPCResponse = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(resume_id)),
|
||||
)
|
||||
.await??;
|
||||
let ThreadResumeResponse { thread, .. } = to_response::<ThreadResumeResponse>(resume_resp)?;
|
||||
|
||||
assert_eq!(
|
||||
thread
|
||||
.git_info
|
||||
.as_ref()
|
||||
.and_then(|git| git.branch.as_deref()),
|
||||
Some("feature/pr-branch")
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_resume_without_overrides_does_not_change_updated_at_or_mtime() -> Result<()> {
|
||||
let server = create_mock_responses_server_repeating_assistant("Done").await;
|
||||
|
||||
@@ -12,7 +12,6 @@ use codex_app_server_protocol::ThreadStartedNotification;
|
||||
use codex_app_server_protocol::ThreadStatus;
|
||||
use codex_app_server_protocol::ThreadStatusChangedNotification;
|
||||
use codex_core::config::set_project_trust_level;
|
||||
use codex_protocol::config_types::ServiceTier;
|
||||
use codex_protocol::config_types::TrustLevel;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -181,34 +180,6 @@ model_reasoning_effort = "high"
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_start_accepts_flex_service_tier() -> 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 req_id = mcp
|
||||
.send_thread_start_request(ThreadStartParams {
|
||||
service_tier: Some(Some(ServiceTier::Flex)),
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
|
||||
let resp: JSONRPCResponse = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(req_id)),
|
||||
)
|
||||
.await??;
|
||||
let ThreadStartResponse { service_tier, .. } = to_response::<ThreadStartResponse>(resp)?;
|
||||
|
||||
assert_eq!(service_tier, Some(ServiceTier::Flex));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_start_accepts_metrics_service_name() -> Result<()> {
|
||||
let server = create_mock_responses_server_repeating_assistant("Done").await;
|
||||
|
||||
@@ -37,7 +37,6 @@ async fn windows_sandbox_setup_start_emits_completion_notification() -> Result<(
|
||||
let request_id = mcp
|
||||
.send_windows_sandbox_setup_start_request(WindowsSandboxSetupStartParams {
|
||||
mode: WindowsSandboxSetupMode::Unelevated,
|
||||
cwd: None,
|
||||
})
|
||||
.await?;
|
||||
let response: JSONRPCResponse = timeout(
|
||||
|
||||
6
codex-rs/artifact-presentation/BUILD.bazel
Normal file
6
codex-rs/artifact-presentation/BUILD.bazel
Normal file
@@ -0,0 +1,6 @@
|
||||
load("//:defs.bzl", "codex_rust_crate")
|
||||
|
||||
codex_rust_crate(
|
||||
name = "artifact-presentation",
|
||||
crate_name = "codex_artifact_presentation",
|
||||
)
|
||||
28
codex-rs/artifact-presentation/Cargo.toml
Normal file
28
codex-rs/artifact-presentation/Cargo.toml
Normal file
@@ -0,0 +1,28 @@
|
||||
[package]
|
||||
name = "codex-artifact-presentation"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "codex_artifact_presentation"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
base64 = { workspace = true }
|
||||
image = { workspace = true, features = ["jpeg", "png"] }
|
||||
ppt-rs = { workspace = true }
|
||||
reqwest = { workspace = true, features = ["blocking"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
uuid = { workspace = true, features = ["v4"] }
|
||||
zip = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
tiny_http = { workspace = true }
|
||||
6
codex-rs/artifact-presentation/src/lib.rs
Normal file
6
codex-rs/artifact-presentation/src/lib.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
mod presentation_artifact;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub use presentation_artifact::*;
|
||||
249
codex-rs/artifact-presentation/src/presentation_artifact/api.rs
Normal file
249
codex-rs/artifact-presentation/src/presentation_artifact/api.rs
Normal file
@@ -0,0 +1,249 @@
|
||||
use base64::Engine;
|
||||
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
|
||||
use image::GenericImageView;
|
||||
use image::ImageFormat;
|
||||
use image::codecs::jpeg::JpegEncoder;
|
||||
use image::imageops::FilterType;
|
||||
use ppt_rs::Chart;
|
||||
use ppt_rs::ChartSeries;
|
||||
use ppt_rs::ChartType;
|
||||
use ppt_rs::Hyperlink as PptHyperlink;
|
||||
use ppt_rs::HyperlinkAction as PptHyperlinkAction;
|
||||
use ppt_rs::Image;
|
||||
use ppt_rs::Presentation;
|
||||
use ppt_rs::Shape;
|
||||
use ppt_rs::ShapeFill;
|
||||
use ppt_rs::ShapeLine;
|
||||
use ppt_rs::ShapeType;
|
||||
use ppt_rs::SlideContent;
|
||||
use ppt_rs::SlideLayout;
|
||||
use ppt_rs::TableBuilder;
|
||||
use ppt_rs::TableCell;
|
||||
use ppt_rs::TableRow;
|
||||
use ppt_rs::generator::ArrowSize;
|
||||
use ppt_rs::generator::ArrowType;
|
||||
use ppt_rs::generator::CellAlign;
|
||||
use ppt_rs::generator::Connector;
|
||||
use ppt_rs::generator::ConnectorLine;
|
||||
use ppt_rs::generator::ConnectorType;
|
||||
use ppt_rs::generator::LineDash;
|
||||
use ppt_rs::generator::generate_image_content_type;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use serde_json::Value;
|
||||
use std::collections::BTreeSet;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::io::Cursor;
|
||||
use std::io::Read;
|
||||
use std::io::Seek;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
use zip::ZipArchive;
|
||||
use zip::ZipWriter;
|
||||
use zip::write::SimpleFileOptions;
|
||||
|
||||
const POINT_TO_EMU: u32 = 12_700;
|
||||
const DEFAULT_SLIDE_WIDTH_POINTS: u32 = 720;
|
||||
const DEFAULT_SLIDE_HEIGHT_POINTS: u32 = 540;
|
||||
const DEFAULT_IMPORTED_TITLE_LEFT: u32 = 36;
|
||||
const DEFAULT_IMPORTED_TITLE_TOP: u32 = 24;
|
||||
const DEFAULT_IMPORTED_TITLE_WIDTH: u32 = 648;
|
||||
const DEFAULT_IMPORTED_TITLE_HEIGHT: u32 = 48;
|
||||
const DEFAULT_IMPORTED_CONTENT_LEFT: u32 = 48;
|
||||
const DEFAULT_IMPORTED_CONTENT_TOP: u32 = 96;
|
||||
const DEFAULT_IMPORTED_CONTENT_WIDTH: u32 = 624;
|
||||
const DEFAULT_IMPORTED_CONTENT_HEIGHT: u32 = 324;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum PresentationArtifactError {
|
||||
#[error("missing `artifact_id` for action `{action}`")]
|
||||
MissingArtifactId { action: String },
|
||||
#[error("unknown artifact id `{artifact_id}` for action `{action}`")]
|
||||
UnknownArtifactId { action: String, artifact_id: String },
|
||||
#[error("unknown action `{0}`")]
|
||||
UnknownAction(String),
|
||||
#[error("invalid args for action `{action}`: {message}")]
|
||||
InvalidArgs { action: String, message: String },
|
||||
#[error("unsupported feature for action `{action}`: {message}")]
|
||||
UnsupportedFeature { action: String, message: String },
|
||||
#[error("failed to import PPTX `{path}`: {message}")]
|
||||
ImportFailed { path: PathBuf, message: String },
|
||||
#[error("failed to export PPTX `{path}`: {message}")]
|
||||
ExportFailed { path: PathBuf, message: String },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct PresentationArtifactRequest {
|
||||
pub artifact_id: Option<String>,
|
||||
pub action: String,
|
||||
#[serde(default)]
|
||||
pub args: Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct PresentationArtifactToolRequest {
|
||||
pub artifact_id: Option<String>,
|
||||
pub actions: Vec<PresentationArtifactToolAction>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PresentationArtifactExecutionRequest {
|
||||
pub artifact_id: Option<String>,
|
||||
pub requests: Vec<PresentationArtifactRequest>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct PresentationArtifactToolAction {
|
||||
pub action: String,
|
||||
#[serde(default)]
|
||||
pub args: Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum PathAccessKind {
|
||||
Read,
|
||||
Write,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PathAccessRequirement {
|
||||
pub action: String,
|
||||
pub kind: PathAccessKind,
|
||||
pub path: PathBuf,
|
||||
}
|
||||
|
||||
impl PresentationArtifactRequest {
|
||||
pub fn is_mutating(&self) -> bool {
|
||||
!is_read_only_action(&self.action)
|
||||
}
|
||||
|
||||
pub fn required_path_accesses(
|
||||
&self,
|
||||
cwd: &Path,
|
||||
) -> Result<Vec<PathAccessRequirement>, PresentationArtifactError> {
|
||||
let access = match self.action.as_str() {
|
||||
"import_pptx" => {
|
||||
let args: ImportPptxArgs = parse_args(&self.action, &self.args)?;
|
||||
vec![PathAccessRequirement {
|
||||
action: self.action.clone(),
|
||||
kind: PathAccessKind::Read,
|
||||
path: resolve_path(cwd, &args.path),
|
||||
}]
|
||||
}
|
||||
"export_pptx" => {
|
||||
let args: ExportPptxArgs = parse_args(&self.action, &self.args)?;
|
||||
vec![PathAccessRequirement {
|
||||
action: self.action.clone(),
|
||||
kind: PathAccessKind::Write,
|
||||
path: resolve_path(cwd, &args.path),
|
||||
}]
|
||||
}
|
||||
"export_preview" => {
|
||||
let args: ExportPreviewArgs = parse_args(&self.action, &self.args)?;
|
||||
vec![PathAccessRequirement {
|
||||
action: self.action.clone(),
|
||||
kind: PathAccessKind::Write,
|
||||
path: resolve_path(cwd, &args.path),
|
||||
}]
|
||||
}
|
||||
"add_image" => {
|
||||
let args: AddImageArgs = parse_args(&self.action, &self.args)?;
|
||||
match args.image_source()? {
|
||||
ImageInputSource::Path(path) => vec![PathAccessRequirement {
|
||||
action: self.action.clone(),
|
||||
kind: PathAccessKind::Read,
|
||||
path: resolve_path(cwd, &path),
|
||||
}],
|
||||
ImageInputSource::DataUrl(_)
|
||||
| ImageInputSource::Blob(_)
|
||||
| ImageInputSource::Uri(_)
|
||||
| ImageInputSource::Placeholder => Vec::new(),
|
||||
}
|
||||
}
|
||||
"replace_image" => {
|
||||
let args: ReplaceImageArgs = parse_args(&self.action, &self.args)?;
|
||||
match (
|
||||
&args.path,
|
||||
&args.data_url,
|
||||
&args.blob,
|
||||
&args.uri,
|
||||
&args.prompt,
|
||||
) {
|
||||
(Some(path), None, None, None, None) => vec![PathAccessRequirement {
|
||||
action: self.action.clone(),
|
||||
kind: PathAccessKind::Read,
|
||||
path: resolve_path(cwd, path),
|
||||
}],
|
||||
(None, Some(_), None, None, None)
|
||||
| (None, None, Some(_), None, None)
|
||||
| (None, None, None, Some(_), None)
|
||||
| (None, None, None, None, Some(_)) => Vec::new(),
|
||||
_ => {
|
||||
return Err(PresentationArtifactError::InvalidArgs {
|
||||
action: self.action.clone(),
|
||||
message:
|
||||
"provide exactly one of `path`, `data_url`, `blob`, or `uri`, or provide `prompt` for a placeholder image"
|
||||
.to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => Vec::new(),
|
||||
};
|
||||
Ok(access)
|
||||
}
|
||||
}
|
||||
|
||||
impl PresentationArtifactToolRequest {
|
||||
pub fn is_mutating(&self) -> Result<bool, PresentationArtifactError> {
|
||||
Ok(self.actions.iter().any(|request| !is_read_only_action(&request.action)))
|
||||
}
|
||||
|
||||
pub fn into_execution_request(
|
||||
self,
|
||||
) -> Result<PresentationArtifactExecutionRequest, PresentationArtifactError> {
|
||||
if self.actions.is_empty() {
|
||||
return Err(PresentationArtifactError::InvalidArgs {
|
||||
action: "presentation_artifact".to_string(),
|
||||
message: "`actions` must contain at least one item".to_string(),
|
||||
});
|
||||
}
|
||||
Ok(PresentationArtifactExecutionRequest {
|
||||
artifact_id: self.artifact_id,
|
||||
requests: self
|
||||
.actions
|
||||
.into_iter()
|
||||
.map(|request| PresentationArtifactRequest {
|
||||
artifact_id: None,
|
||||
action: request.action,
|
||||
args: request.args,
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn required_path_accesses(
|
||||
&self,
|
||||
cwd: &Path,
|
||||
) -> Result<Vec<PathAccessRequirement>, PresentationArtifactError> {
|
||||
let mut accesses = Vec::new();
|
||||
for request in &self.actions {
|
||||
accesses.extend(
|
||||
PresentationArtifactRequest {
|
||||
artifact_id: None,
|
||||
action: request.action.clone(),
|
||||
args: request.args.clone(),
|
||||
}
|
||||
.required_path_accesses(cwd)?,
|
||||
);
|
||||
}
|
||||
Ok(accesses)
|
||||
}
|
||||
}
|
||||
729
codex-rs/artifact-presentation/src/presentation_artifact/args.rs
Normal file
729
codex-rs/artifact-presentation/src/presentation_artifact/args.rs
Normal file
@@ -0,0 +1,729 @@
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct CreateArgs {
|
||||
name: Option<String>,
|
||||
slide_size: Option<Value>,
|
||||
theme: Option<ThemeArgs>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ImportPptxArgs {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ExportPptxArgs {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ExportPreviewArgs {
|
||||
path: PathBuf,
|
||||
slide_index: Option<u32>,
|
||||
format: Option<String>,
|
||||
scale: Option<f32>,
|
||||
quality: Option<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
struct AddSlideArgs {
|
||||
layout: Option<String>,
|
||||
notes: Option<String>,
|
||||
background_fill: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct CreateLayoutArgs {
|
||||
name: String,
|
||||
kind: Option<String>,
|
||||
parent_layout_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) enum PreviewOutputFormat {
|
||||
Png,
|
||||
Jpeg,
|
||||
Svg,
|
||||
}
|
||||
|
||||
impl PreviewOutputFormat {
|
||||
fn extension(self) -> &'static str {
|
||||
match self {
|
||||
Self::Png => "png",
|
||||
Self::Jpeg => "jpg",
|
||||
Self::Svg => "svg",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct AddLayoutPlaceholderArgs {
|
||||
layout_id: String,
|
||||
name: String,
|
||||
placeholder_type: String,
|
||||
index: Option<u32>,
|
||||
text: Option<String>,
|
||||
geometry: Option<String>,
|
||||
position: Option<PositionArgs>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct LayoutIdArgs {
|
||||
layout_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct SetSlideLayoutArgs {
|
||||
slide_index: u32,
|
||||
layout_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct UpdatePlaceholderTextArgs {
|
||||
slide_index: u32,
|
||||
name: String,
|
||||
text: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct NotesArgs {
|
||||
slide_index: u32,
|
||||
text: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct NotesVisibilityArgs {
|
||||
slide_index: u32,
|
||||
visible: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ThemeArgs {
|
||||
color_scheme: HashMap<String, String>,
|
||||
major_font: Option<String>,
|
||||
minor_font: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct StyleNameArgs {
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct AddStyleArgs {
|
||||
name: String,
|
||||
#[serde(flatten)]
|
||||
styling: TextStylingArgs,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct InspectArgs {
|
||||
kind: Option<String>,
|
||||
include: Option<String>,
|
||||
exclude: Option<String>,
|
||||
search: Option<String>,
|
||||
target_id: Option<String>,
|
||||
target: Option<InspectTargetArgs>,
|
||||
max_chars: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct InspectTargetArgs {
|
||||
id: String,
|
||||
before_lines: Option<usize>,
|
||||
after_lines: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ResolveArgs {
|
||||
id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct PatchOperationInput {
|
||||
artifact_id: Option<String>,
|
||||
action: String,
|
||||
#[serde(default)]
|
||||
args: Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct RecordPatchArgs {
|
||||
operations: Vec<PatchOperationInput>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ApplyPatchArgs {
|
||||
operations: Option<Vec<PatchOperationInput>>,
|
||||
patch: Option<PresentationPatch>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct PresentationPatch {
|
||||
version: u32,
|
||||
artifact_id: String,
|
||||
operations: Vec<PatchOperation>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct PatchOperation {
|
||||
action: String,
|
||||
#[serde(default)]
|
||||
args: Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
struct InsertSlideArgs {
|
||||
index: Option<u32>,
|
||||
after_slide_index: Option<u32>,
|
||||
layout: Option<String>,
|
||||
notes: Option<String>,
|
||||
background_fill: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct SlideIndexArgs {
|
||||
slide_index: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct MoveSlideArgs {
|
||||
from_index: u32,
|
||||
to_index: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct SetActiveSlideArgs {
|
||||
slide_index: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct SetSlideBackgroundArgs {
|
||||
slide_index: u32,
|
||||
fill: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct PositionArgs {
|
||||
left: u32,
|
||||
top: u32,
|
||||
width: u32,
|
||||
height: u32,
|
||||
rotation: Option<i32>,
|
||||
flip_horizontal: Option<bool>,
|
||||
flip_vertical: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize)]
|
||||
struct PartialPositionArgs {
|
||||
left: Option<u32>,
|
||||
top: Option<u32>,
|
||||
width: Option<u32>,
|
||||
height: Option<u32>,
|
||||
rotation: Option<i32>,
|
||||
flip_horizontal: Option<bool>,
|
||||
flip_vertical: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize)]
|
||||
struct TextStylingArgs {
|
||||
style: Option<String>,
|
||||
font_size: Option<u32>,
|
||||
font_family: Option<String>,
|
||||
color: Option<String>,
|
||||
fill: Option<String>,
|
||||
alignment: Option<String>,
|
||||
bold: Option<bool>,
|
||||
italic: Option<bool>,
|
||||
underline: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize)]
|
||||
struct TextLayoutArgs {
|
||||
insets: Option<TextInsetsArgs>,
|
||||
wrap: Option<String>,
|
||||
auto_fit: Option<String>,
|
||||
vertical_alignment: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct TextInsetsArgs {
|
||||
left: u32,
|
||||
right: u32,
|
||||
top: u32,
|
||||
bottom: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum RichTextInput {
|
||||
Plain(String),
|
||||
Paragraphs(Vec<RichParagraphInput>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum RichParagraphInput {
|
||||
Plain(String),
|
||||
Runs(Vec<RichRunInput>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum RichRunInput {
|
||||
Plain(String),
|
||||
Styled(RichRunObjectInput),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct RichRunObjectInput {
|
||||
run: String,
|
||||
#[serde(default)]
|
||||
text_style: TextStylingArgs,
|
||||
link: Option<RichTextLinkInput>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct RichTextLinkInput {
|
||||
uri: Option<String>,
|
||||
is_external: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct AddTextShapeArgs {
|
||||
slide_index: u32,
|
||||
text: String,
|
||||
position: PositionArgs,
|
||||
#[serde(flatten)]
|
||||
styling: TextStylingArgs,
|
||||
#[serde(default)]
|
||||
text_layout: TextLayoutArgs,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize)]
|
||||
struct StrokeArgs {
|
||||
color: String,
|
||||
width: u32,
|
||||
style: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct AddShapeArgs {
|
||||
slide_index: u32,
|
||||
geometry: String,
|
||||
position: PositionArgs,
|
||||
fill: Option<String>,
|
||||
stroke: Option<StrokeArgs>,
|
||||
text: Option<String>,
|
||||
rotation: Option<i32>,
|
||||
flip_horizontal: Option<bool>,
|
||||
flip_vertical: Option<bool>,
|
||||
#[serde(default)]
|
||||
text_style: TextStylingArgs,
|
||||
#[serde(default)]
|
||||
text_layout: TextLayoutArgs,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize)]
|
||||
struct ConnectorLineArgs {
|
||||
color: Option<String>,
|
||||
width: Option<u32>,
|
||||
style: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct PointArgs {
|
||||
left: u32,
|
||||
top: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct AddConnectorArgs {
|
||||
slide_index: u32,
|
||||
connector_type: String,
|
||||
start: PointArgs,
|
||||
end: PointArgs,
|
||||
line: Option<ConnectorLineArgs>,
|
||||
start_arrow: Option<String>,
|
||||
end_arrow: Option<String>,
|
||||
arrow_size: Option<String>,
|
||||
label: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct AddImageArgs {
|
||||
slide_index: u32,
|
||||
path: Option<PathBuf>,
|
||||
data_url: Option<String>,
|
||||
blob: Option<String>,
|
||||
uri: Option<String>,
|
||||
position: PositionArgs,
|
||||
fit: Option<ImageFitMode>,
|
||||
crop: Option<ImageCropArgs>,
|
||||
rotation: Option<i32>,
|
||||
flip_horizontal: Option<bool>,
|
||||
flip_vertical: Option<bool>,
|
||||
lock_aspect_ratio: Option<bool>,
|
||||
alt: Option<String>,
|
||||
prompt: Option<String>,
|
||||
}
|
||||
|
||||
impl AddImageArgs {
|
||||
fn image_source(&self) -> Result<ImageInputSource, PresentationArtifactError> {
|
||||
match (&self.path, &self.data_url, &self.blob, &self.uri) {
|
||||
(Some(path), None, None, None) => Ok(ImageInputSource::Path(path.clone())),
|
||||
(None, Some(data_url), None, None) => Ok(ImageInputSource::DataUrl(data_url.clone())),
|
||||
(None, None, Some(blob), None) => Ok(ImageInputSource::Blob(blob.clone())),
|
||||
(None, None, None, Some(uri)) => Ok(ImageInputSource::Uri(uri.clone())),
|
||||
(None, None, None, None) if self.prompt.is_some() => Ok(ImageInputSource::Placeholder),
|
||||
_ => Err(PresentationArtifactError::InvalidArgs {
|
||||
action: "add_image".to_string(),
|
||||
message:
|
||||
"provide exactly one of `path`, `data_url`, `blob`, or `uri`, or provide `prompt` for a placeholder image"
|
||||
.to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ImageInputSource {
|
||||
Path(PathBuf),
|
||||
DataUrl(String),
|
||||
Blob(String),
|
||||
Uri(String),
|
||||
Placeholder,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct ImageCropArgs {
|
||||
left: f64,
|
||||
top: f64,
|
||||
right: f64,
|
||||
bottom: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct AddTableArgs {
|
||||
slide_index: u32,
|
||||
position: PositionArgs,
|
||||
rows: Vec<Vec<Value>>,
|
||||
column_widths: Option<Vec<u32>>,
|
||||
row_heights: Option<Vec<u32>>,
|
||||
style: Option<String>,
|
||||
style_options: Option<TableStyleOptionsArgs>,
|
||||
borders: Option<TableBordersArgs>,
|
||||
right_to_left: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct AddChartArgs {
|
||||
slide_index: u32,
|
||||
position: PositionArgs,
|
||||
chart_type: String,
|
||||
categories: Vec<String>,
|
||||
series: Vec<ChartSeriesArgs>,
|
||||
title: Option<String>,
|
||||
style_index: Option<u32>,
|
||||
has_legend: Option<bool>,
|
||||
legend_position: Option<String>,
|
||||
#[serde(default)]
|
||||
legend_text_style: TextStylingArgs,
|
||||
x_axis_title: Option<String>,
|
||||
y_axis_title: Option<String>,
|
||||
data_labels: Option<ChartDataLabelsArgs>,
|
||||
chart_fill: Option<String>,
|
||||
plot_area_fill: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ChartSeriesArgs {
|
||||
name: String,
|
||||
values: Vec<f64>,
|
||||
categories: Option<Vec<String>>,
|
||||
x_values: Option<Vec<f64>>,
|
||||
fill: Option<String>,
|
||||
stroke: Option<StrokeArgs>,
|
||||
marker: Option<ChartMarkerArgs>,
|
||||
data_label_overrides: Option<Vec<ChartDataLabelOverrideArgs>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct ChartMarkerArgs {
|
||||
symbol: Option<String>,
|
||||
size: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct ChartDataLabelsArgs {
|
||||
show_value: Option<bool>,
|
||||
show_category_name: Option<bool>,
|
||||
show_leader_lines: Option<bool>,
|
||||
position: Option<String>,
|
||||
#[serde(default)]
|
||||
text_style: TextStylingArgs,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct ChartDataLabelOverrideArgs {
|
||||
idx: u32,
|
||||
text: Option<String>,
|
||||
position: Option<String>,
|
||||
#[serde(default)]
|
||||
text_style: TextStylingArgs,
|
||||
fill: Option<String>,
|
||||
stroke: Option<StrokeArgs>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct UpdateTextArgs {
|
||||
element_id: String,
|
||||
text: String,
|
||||
#[serde(default)]
|
||||
styling: TextStylingArgs,
|
||||
#[serde(default)]
|
||||
text_layout: TextLayoutArgs,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct SetRichTextArgs {
|
||||
element_id: Option<String>,
|
||||
slide_index: Option<u32>,
|
||||
row: Option<u32>,
|
||||
column: Option<u32>,
|
||||
notes: Option<bool>,
|
||||
text: RichTextInput,
|
||||
#[serde(default)]
|
||||
styling: TextStylingArgs,
|
||||
#[serde(default)]
|
||||
text_layout: TextLayoutArgs,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct FormatTextRangeArgs {
|
||||
element_id: Option<String>,
|
||||
slide_index: Option<u32>,
|
||||
row: Option<u32>,
|
||||
column: Option<u32>,
|
||||
notes: Option<bool>,
|
||||
query: Option<String>,
|
||||
occurrence: Option<usize>,
|
||||
start_cp: Option<usize>,
|
||||
length: Option<usize>,
|
||||
#[serde(default)]
|
||||
styling: TextStylingArgs,
|
||||
#[serde(default)]
|
||||
text_layout: TextLayoutArgs,
|
||||
link: Option<RichTextLinkInput>,
|
||||
spacing_before: Option<u32>,
|
||||
spacing_after: Option<u32>,
|
||||
line_spacing: Option<f32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ReplaceTextArgs {
|
||||
element_id: String,
|
||||
search: String,
|
||||
replace: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct InsertTextAfterArgs {
|
||||
element_id: String,
|
||||
after: String,
|
||||
insert: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct SetHyperlinkArgs {
|
||||
element_id: String,
|
||||
link_type: Option<String>,
|
||||
url: Option<String>,
|
||||
slide_index: Option<u32>,
|
||||
address: Option<String>,
|
||||
subject: Option<String>,
|
||||
path: Option<String>,
|
||||
tooltip: Option<String>,
|
||||
highlight_click: Option<bool>,
|
||||
clear: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct UpdateShapeStyleArgs {
|
||||
element_id: String,
|
||||
position: Option<PartialPositionArgs>,
|
||||
fill: Option<String>,
|
||||
stroke: Option<StrokeArgs>,
|
||||
rotation: Option<i32>,
|
||||
flip_horizontal: Option<bool>,
|
||||
flip_vertical: Option<bool>,
|
||||
fit: Option<ImageFitMode>,
|
||||
crop: Option<ImageCropArgs>,
|
||||
lock_aspect_ratio: Option<bool>,
|
||||
z_order: Option<u32>,
|
||||
#[serde(default)]
|
||||
text_layout: TextLayoutArgs,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ElementIdArgs {
|
||||
element_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ReplaceImageArgs {
|
||||
element_id: String,
|
||||
path: Option<PathBuf>,
|
||||
data_url: Option<String>,
|
||||
blob: Option<String>,
|
||||
uri: Option<String>,
|
||||
fit: Option<ImageFitMode>,
|
||||
crop: Option<ImageCropArgs>,
|
||||
rotation: Option<i32>,
|
||||
flip_horizontal: Option<bool>,
|
||||
flip_vertical: Option<bool>,
|
||||
lock_aspect_ratio: Option<bool>,
|
||||
alt: Option<String>,
|
||||
prompt: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct UpdateTableCellArgs {
|
||||
element_id: String,
|
||||
row: u32,
|
||||
column: u32,
|
||||
value: Value,
|
||||
#[serde(default)]
|
||||
styling: TextStylingArgs,
|
||||
background_fill: Option<String>,
|
||||
alignment: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct TableStyleOptionsArgs {
|
||||
header_row: Option<bool>,
|
||||
banded_rows: Option<bool>,
|
||||
banded_columns: Option<bool>,
|
||||
first_column: Option<bool>,
|
||||
last_column: Option<bool>,
|
||||
total_row: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct TableBorderArgs {
|
||||
color: String,
|
||||
width: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct TableBordersArgs {
|
||||
outside: Option<TableBorderArgs>,
|
||||
inside: Option<TableBorderArgs>,
|
||||
top: Option<TableBorderArgs>,
|
||||
bottom: Option<TableBorderArgs>,
|
||||
left: Option<TableBorderArgs>,
|
||||
right: Option<TableBorderArgs>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct UpdateTableStyleArgs {
|
||||
element_id: String,
|
||||
style: Option<String>,
|
||||
style_options: Option<TableStyleOptionsArgs>,
|
||||
borders: Option<TableBordersArgs>,
|
||||
right_to_left: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct StyleTableBlockArgs {
|
||||
element_id: String,
|
||||
row: u32,
|
||||
column: u32,
|
||||
row_count: u32,
|
||||
column_count: u32,
|
||||
#[serde(default)]
|
||||
styling: TextStylingArgs,
|
||||
background_fill: Option<String>,
|
||||
alignment: Option<String>,
|
||||
borders: Option<TableBordersArgs>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct MergeTableCellsArgs {
|
||||
element_id: String,
|
||||
start_row: u32,
|
||||
end_row: u32,
|
||||
start_column: u32,
|
||||
end_column: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct UpdateChartArgs {
|
||||
element_id: String,
|
||||
title: Option<String>,
|
||||
categories: Option<Vec<String>>,
|
||||
style_index: Option<u32>,
|
||||
has_legend: Option<bool>,
|
||||
legend_position: Option<String>,
|
||||
#[serde(default)]
|
||||
legend_text_style: TextStylingArgs,
|
||||
x_axis_title: Option<String>,
|
||||
y_axis_title: Option<String>,
|
||||
data_labels: Option<ChartDataLabelsArgs>,
|
||||
chart_fill: Option<String>,
|
||||
plot_area_fill: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct AddChartSeriesArgs {
|
||||
element_id: String,
|
||||
name: String,
|
||||
values: Vec<f64>,
|
||||
categories: Option<Vec<String>>,
|
||||
x_values: Option<Vec<f64>>,
|
||||
fill: Option<String>,
|
||||
stroke: Option<StrokeArgs>,
|
||||
marker: Option<ChartMarkerArgs>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct SetCommentAuthorArgs {
|
||||
display_name: String,
|
||||
initials: String,
|
||||
email: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct CommentPositionArgs {
|
||||
x: u32,
|
||||
y: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct AddCommentThreadArgs {
|
||||
slide_index: Option<u32>,
|
||||
element_id: Option<String>,
|
||||
query: Option<String>,
|
||||
occurrence: Option<usize>,
|
||||
start_cp: Option<usize>,
|
||||
length: Option<usize>,
|
||||
text: String,
|
||||
position: Option<CommentPositionArgs>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct AddCommentReplyArgs {
|
||||
thread_id: String,
|
||||
text: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ToggleCommentReactionArgs {
|
||||
thread_id: String,
|
||||
message_id: Option<String>,
|
||||
emoji: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct CommentThreadIdArgs {
|
||||
thread_id: String,
|
||||
}
|
||||
@@ -0,0 +1,871 @@
|
||||
fn inspect_document(document: &PresentationDocument, args: &InspectArgs) -> String {
|
||||
let include_kinds = args
|
||||
.include
|
||||
.as_deref()
|
||||
.or(args.kind.as_deref())
|
||||
.unwrap_or(
|
||||
"deck,slide,textbox,shape,connector,table,chart,image,notes,layoutList,textRange,comment",
|
||||
);
|
||||
let included_kinds = include_kinds
|
||||
.split(',')
|
||||
.map(str::trim)
|
||||
.filter(|entry| !entry.is_empty())
|
||||
.collect::<HashSet<_>>();
|
||||
let excluded_kinds = args
|
||||
.exclude
|
||||
.as_deref()
|
||||
.unwrap_or_default()
|
||||
.split(',')
|
||||
.map(str::trim)
|
||||
.filter(|entry| !entry.is_empty())
|
||||
.collect::<HashSet<_>>();
|
||||
let include = |name: &str| included_kinds.contains(name) && !excluded_kinds.contains(name);
|
||||
let mut records: Vec<(Value, Option<String>)> = Vec::new();
|
||||
if include("deck") {
|
||||
records.push((
|
||||
serde_json::json!({
|
||||
"kind": "deck",
|
||||
"id": format!("pr/{}", document.artifact_id),
|
||||
"name": document.name,
|
||||
"slides": document.slides.len(),
|
||||
"styleIds": document
|
||||
.named_text_styles()
|
||||
.iter()
|
||||
.map(|style| format!("st/{}", style.name))
|
||||
.collect::<Vec<_>>(),
|
||||
"activeSlideIndex": document.active_slide_index,
|
||||
"activeSlideId": document.active_slide_index.and_then(|index| document.slides.get(index)).map(|slide| format!("sl/{}", slide.slide_id)),
|
||||
"commentThreadIds": document
|
||||
.comment_threads
|
||||
.iter()
|
||||
.map(|thread| format!("th/{}", thread.thread_id))
|
||||
.collect::<Vec<_>>(),
|
||||
}),
|
||||
None,
|
||||
));
|
||||
}
|
||||
if include("styleList") {
|
||||
for style in document.named_text_styles() {
|
||||
records.push((named_text_style_to_json(&style, "st"), None));
|
||||
}
|
||||
}
|
||||
if include("layoutList") {
|
||||
for layout in &document.layouts {
|
||||
let placeholders = resolved_layout_placeholders(document, &layout.layout_id, "inspect")
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|placeholder| {
|
||||
serde_json::json!({
|
||||
"name": placeholder.definition.name,
|
||||
"type": placeholder.definition.placeholder_type,
|
||||
"sourceLayoutId": placeholder.source_layout_id,
|
||||
"textPreview": placeholder.definition.text,
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
records.push((
|
||||
serde_json::json!({
|
||||
"kind": "layout",
|
||||
"id": format!("ly/{}", layout.layout_id),
|
||||
"layoutId": layout.layout_id,
|
||||
"name": layout.name,
|
||||
"type": match layout.kind { LayoutKind::Layout => "layout", LayoutKind::Master => "master" },
|
||||
"parentLayoutId": layout.parent_layout_id,
|
||||
"placeholders": placeholders,
|
||||
}),
|
||||
None,
|
||||
));
|
||||
}
|
||||
}
|
||||
for (index, slide) in document.slides.iter().enumerate() {
|
||||
let slide_id = format!("sl/{}", slide.slide_id);
|
||||
if include("slide") {
|
||||
records.push((
|
||||
serde_json::json!({
|
||||
"kind": "slide",
|
||||
"id": slide_id,
|
||||
"slide": index + 1,
|
||||
"slideIndex": index,
|
||||
"isActive": document.active_slide_index == Some(index),
|
||||
"layoutId": slide.layout_id,
|
||||
"elements": slide.elements.len(),
|
||||
}),
|
||||
Some(slide_id.clone()),
|
||||
));
|
||||
}
|
||||
if include("notes") && !slide.notes.text.is_empty() {
|
||||
records.push((
|
||||
serde_json::json!({
|
||||
"kind": "notes",
|
||||
"id": format!("nt/{}", slide.slide_id),
|
||||
"slide": index + 1,
|
||||
"visible": slide.notes.visible,
|
||||
"text": slide.notes.text,
|
||||
"textPreview": slide.notes.text.replace('\n', " | "),
|
||||
"textChars": slide.notes.text.chars().count(),
|
||||
"textLines": slide.notes.text.lines().count(),
|
||||
"richText": rich_text_to_proto(&slide.notes.text, &slide.notes.rich_text),
|
||||
}),
|
||||
Some(slide_id.clone()),
|
||||
));
|
||||
}
|
||||
if include("textRange") {
|
||||
records.extend(
|
||||
slide
|
||||
.notes
|
||||
.rich_text
|
||||
.ranges
|
||||
.iter()
|
||||
.map(|range| {
|
||||
let mut record = text_range_to_proto(&slide.notes.text, range);
|
||||
record["kind"] = Value::String("textRange".to_string());
|
||||
record["slide"] = Value::from(index + 1);
|
||||
record["slideIndex"] = Value::from(index);
|
||||
record["hostAnchor"] = Value::String(format!("nt/{}", slide.slide_id));
|
||||
record["hostKind"] = Value::String("notes".to_string());
|
||||
(record, Some(slide_id.clone()))
|
||||
}),
|
||||
);
|
||||
}
|
||||
for element in &slide.elements {
|
||||
let mut record = match element {
|
||||
PresentationElement::Text(text) => {
|
||||
if !include("textbox") {
|
||||
continue;
|
||||
}
|
||||
serde_json::json!({
|
||||
"kind": "textbox",
|
||||
"id": format!("sh/{}", text.element_id),
|
||||
"slide": index + 1,
|
||||
"text": text.text,
|
||||
"textStyle": text_style_to_proto(&text.style),
|
||||
"textPreview": text.text.replace('\n', " | "),
|
||||
"textChars": text.text.chars().count(),
|
||||
"textLines": text.text.lines().count(),
|
||||
"richText": rich_text_to_proto(&text.text, &text.rich_text),
|
||||
"bbox": [text.frame.left, text.frame.top, text.frame.width, text.frame.height],
|
||||
"bboxUnit": "points",
|
||||
})
|
||||
}
|
||||
PresentationElement::Shape(shape) => {
|
||||
if !(include("shape") || include("textbox") && shape.text.is_some()) {
|
||||
continue;
|
||||
}
|
||||
let kind = if shape.text.is_some() && include("textbox") {
|
||||
"textbox"
|
||||
} else {
|
||||
"shape"
|
||||
};
|
||||
let mut record = serde_json::json!({
|
||||
"kind": kind,
|
||||
"id": format!("sh/{}", shape.element_id),
|
||||
"slide": index + 1,
|
||||
"geometry": format!("{:?}", shape.geometry),
|
||||
"text": shape.text,
|
||||
"textStyle": text_style_to_proto(&shape.text_style),
|
||||
"richText": shape
|
||||
.text
|
||||
.as_ref()
|
||||
.zip(shape.rich_text.as_ref())
|
||||
.map(|(text, rich_text)| rich_text_to_proto(text, rich_text))
|
||||
.unwrap_or(Value::Null),
|
||||
"rotation": shape.rotation_degrees,
|
||||
"flipHorizontal": shape.flip_horizontal,
|
||||
"flipVertical": shape.flip_vertical,
|
||||
"bbox": [shape.frame.left, shape.frame.top, shape.frame.width, shape.frame.height],
|
||||
"bboxUnit": "points",
|
||||
});
|
||||
if let Some(text) = &shape.text {
|
||||
record["textPreview"] = Value::String(text.replace('\n', " | "));
|
||||
record["textChars"] = Value::from(text.chars().count());
|
||||
record["textLines"] = Value::from(text.lines().count());
|
||||
}
|
||||
record
|
||||
}
|
||||
PresentationElement::Connector(connector) => {
|
||||
if !include("shape") && !include("connector") {
|
||||
continue;
|
||||
}
|
||||
serde_json::json!({
|
||||
"kind": "connector",
|
||||
"id": format!("cn/{}", connector.element_id),
|
||||
"slide": index + 1,
|
||||
"connectorType": format!("{:?}", connector.connector_type),
|
||||
"start": [connector.start.left, connector.start.top],
|
||||
"end": [connector.end.left, connector.end.top],
|
||||
"lineStyle": format!("{:?}", connector.line_style),
|
||||
"label": connector.label,
|
||||
})
|
||||
}
|
||||
PresentationElement::Table(table) => {
|
||||
if !include("table") {
|
||||
continue;
|
||||
}
|
||||
serde_json::json!({
|
||||
"kind": "table",
|
||||
"id": format!("tb/{}", table.element_id),
|
||||
"slide": index + 1,
|
||||
"rows": table.rows.len(),
|
||||
"cols": table.rows.iter().map(std::vec::Vec::len).max().unwrap_or(0),
|
||||
"columnWidths": table.column_widths,
|
||||
"rowHeights": table.row_heights,
|
||||
"preview": table.rows.first().map(|row| row.iter().map(|cell| cell.text.clone()).collect::<Vec<_>>().join(" | ")),
|
||||
"style": table.style,
|
||||
"styleOptions": table_style_options_to_proto(&table.style_options),
|
||||
"borders": table.borders.as_ref().map(table_borders_to_proto),
|
||||
"rightToLeft": table.right_to_left,
|
||||
"cellTextStyles": table
|
||||
.rows
|
||||
.iter()
|
||||
.map(|row| row.iter().map(|cell| text_style_to_proto(&cell.text_style)).collect::<Vec<_>>())
|
||||
.collect::<Vec<_>>(),
|
||||
"rowsData": table
|
||||
.rows
|
||||
.iter()
|
||||
.map(|row| row.iter().map(table_cell_to_proto).collect::<Vec<_>>())
|
||||
.collect::<Vec<_>>(),
|
||||
"bbox": [table.frame.left, table.frame.top, table.frame.width, table.frame.height],
|
||||
"bboxUnit": "points",
|
||||
})
|
||||
}
|
||||
PresentationElement::Chart(chart) => {
|
||||
if !include("chart") {
|
||||
continue;
|
||||
}
|
||||
serde_json::json!({
|
||||
"kind": "chart",
|
||||
"id": format!("ch/{}", chart.element_id),
|
||||
"slide": index + 1,
|
||||
"chartType": format!("{:?}", chart.chart_type),
|
||||
"title": chart.title,
|
||||
"styleIndex": chart.style_index,
|
||||
"hasLegend": chart.has_legend,
|
||||
"legend": chart.legend.as_ref().map(chart_legend_to_proto),
|
||||
"xAxis": chart.x_axis.as_ref().map(chart_axis_to_proto),
|
||||
"yAxis": chart.y_axis.as_ref().map(chart_axis_to_proto),
|
||||
"dataLabels": chart.data_labels.as_ref().map(chart_data_labels_to_proto),
|
||||
"chartFill": chart.chart_fill,
|
||||
"plotAreaFill": chart.plot_area_fill,
|
||||
"series": chart
|
||||
.series
|
||||
.iter()
|
||||
.map(|series| serde_json::json!({
|
||||
"name": series.name,
|
||||
"values": series.values,
|
||||
"categories": series.categories,
|
||||
"xValues": series.x_values,
|
||||
"fill": series.fill,
|
||||
"stroke": series.stroke.as_ref().map(stroke_to_proto),
|
||||
"marker": series.marker.as_ref().map(chart_marker_to_proto),
|
||||
"dataLabelOverrides": series
|
||||
.data_label_overrides
|
||||
.iter()
|
||||
.map(chart_data_label_override_to_proto)
|
||||
.collect::<Vec<_>>(),
|
||||
}))
|
||||
.collect::<Vec<_>>(),
|
||||
"bbox": [chart.frame.left, chart.frame.top, chart.frame.width, chart.frame.height],
|
||||
"bboxUnit": "points",
|
||||
})
|
||||
}
|
||||
PresentationElement::Image(image) => {
|
||||
if !include("image") {
|
||||
continue;
|
||||
}
|
||||
serde_json::json!({
|
||||
"kind": "image",
|
||||
"id": format!("im/{}", image.element_id),
|
||||
"slide": index + 1,
|
||||
"alt": image.alt_text,
|
||||
"prompt": image.prompt,
|
||||
"fit": format!("{:?}", image.fit_mode),
|
||||
"rotation": image.rotation_degrees,
|
||||
"flipHorizontal": image.flip_horizontal,
|
||||
"flipVertical": image.flip_vertical,
|
||||
"crop": image.crop.map(|(left, top, right, bottom)| serde_json::json!({
|
||||
"left": left,
|
||||
"top": top,
|
||||
"right": right,
|
||||
"bottom": bottom,
|
||||
})),
|
||||
"isPlaceholder": image.is_placeholder,
|
||||
"lockAspectRatio": image.lock_aspect_ratio,
|
||||
"bbox": [image.frame.left, image.frame.top, image.frame.width, image.frame.height],
|
||||
"bboxUnit": "points",
|
||||
})
|
||||
}
|
||||
};
|
||||
if let Some(placeholder) = match element {
|
||||
PresentationElement::Text(text) => text.placeholder.as_ref(),
|
||||
PresentationElement::Shape(shape) => shape.placeholder.as_ref(),
|
||||
PresentationElement::Connector(_)
|
||||
| PresentationElement::Table(_)
|
||||
| PresentationElement::Chart(_) => None,
|
||||
PresentationElement::Image(image) => image.placeholder.as_ref(),
|
||||
} {
|
||||
record["placeholder"] = Value::String(placeholder.placeholder_type.clone());
|
||||
record["placeholderName"] = Value::String(placeholder.name.clone());
|
||||
record["placeholderIndex"] =
|
||||
placeholder.index.map(Value::from).unwrap_or(Value::Null);
|
||||
}
|
||||
if let PresentationElement::Shape(shape) = element
|
||||
&& let Some(stroke) = &shape.stroke
|
||||
{
|
||||
record["stroke"] = serde_json::json!({
|
||||
"color": stroke.color,
|
||||
"width": stroke.width,
|
||||
"style": stroke.style.as_api_str(),
|
||||
});
|
||||
}
|
||||
if let Some(hyperlink) = match element {
|
||||
PresentationElement::Text(text) => text.hyperlink.as_ref(),
|
||||
PresentationElement::Shape(shape) => shape.hyperlink.as_ref(),
|
||||
PresentationElement::Connector(_)
|
||||
| PresentationElement::Image(_)
|
||||
| PresentationElement::Table(_)
|
||||
| PresentationElement::Chart(_) => None,
|
||||
} {
|
||||
record["hyperlink"] = hyperlink.to_json();
|
||||
}
|
||||
records.push((record, Some(slide_id.clone())));
|
||||
if include("textRange") {
|
||||
match element {
|
||||
PresentationElement::Text(text) => {
|
||||
records.extend(text.rich_text.ranges.iter().map(|range| {
|
||||
let mut record = text_range_to_proto(&text.text, range);
|
||||
record["kind"] = Value::String("textRange".to_string());
|
||||
record["slide"] = Value::from(index + 1);
|
||||
record["slideIndex"] = Value::from(index);
|
||||
record["hostAnchor"] = Value::String(format!("sh/{}", text.element_id));
|
||||
record["hostKind"] = Value::String("textbox".to_string());
|
||||
(record, Some(slide_id.clone()))
|
||||
}));
|
||||
}
|
||||
PresentationElement::Shape(shape) => {
|
||||
if let Some((text, rich_text)) = shape.text.as_ref().zip(shape.rich_text.as_ref()) {
|
||||
records.extend(rich_text.ranges.iter().map(|range| {
|
||||
let mut record = text_range_to_proto(text, range);
|
||||
record["kind"] = Value::String("textRange".to_string());
|
||||
record["slide"] = Value::from(index + 1);
|
||||
record["slideIndex"] = Value::from(index);
|
||||
record["hostAnchor"] = Value::String(format!("sh/{}", shape.element_id));
|
||||
record["hostKind"] = Value::String("textbox".to_string());
|
||||
(record, Some(slide_id.clone()))
|
||||
}));
|
||||
}
|
||||
}
|
||||
PresentationElement::Table(table) => {
|
||||
for (row_index, row) in table.rows.iter().enumerate() {
|
||||
for (column_index, cell) in row.iter().enumerate() {
|
||||
records.extend(cell.rich_text.ranges.iter().map(|range| {
|
||||
let mut record = text_range_to_proto(&cell.text, range);
|
||||
record["kind"] = Value::String("textRange".to_string());
|
||||
record["slide"] = Value::from(index + 1);
|
||||
record["slideIndex"] = Value::from(index);
|
||||
record["hostAnchor"] = Value::String(format!(
|
||||
"tb/{}#cell/{row_index}/{column_index}",
|
||||
table.element_id
|
||||
));
|
||||
record["hostKind"] = Value::String("tableCell".to_string());
|
||||
(record, Some(slide_id.clone()))
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
PresentationElement::Connector(_)
|
||||
| PresentationElement::Image(_)
|
||||
| PresentationElement::Chart(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if include("comment") {
|
||||
records.extend(document.comment_threads.iter().map(|thread| {
|
||||
let mut record = comment_thread_to_proto(thread);
|
||||
record["id"] = Value::String(format!("th/{}", thread.thread_id));
|
||||
(record, None)
|
||||
}));
|
||||
}
|
||||
|
||||
if let Some(target_id) = args.target_id.as_deref() {
|
||||
records.retain(|(record, slide_id)| {
|
||||
legacy_target_matches(target_id, record, slide_id.as_deref())
|
||||
});
|
||||
if records.is_empty() {
|
||||
records.push((
|
||||
serde_json::json!({
|
||||
"kind": "notice",
|
||||
"noticeType": "targetNotFound",
|
||||
"target": { "id": target_id },
|
||||
"message": format!("No inspect records matched target `{target_id}`."),
|
||||
}),
|
||||
None,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(search) = args.search.as_deref() {
|
||||
let search_lowercase = search.to_ascii_lowercase();
|
||||
records.retain(|(record, _)| {
|
||||
record
|
||||
.to_string()
|
||||
.to_ascii_lowercase()
|
||||
.contains(&search_lowercase)
|
||||
});
|
||||
if records.is_empty() {
|
||||
records.push((
|
||||
serde_json::json!({
|
||||
"kind": "notice",
|
||||
"noticeType": "noMatches",
|
||||
"search": search,
|
||||
"message": format!("No inspect records matched search `{search}`."),
|
||||
}),
|
||||
None,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(target) = args.target.as_ref() {
|
||||
if let Some(target_index) = records.iter().position(|(record, _)| {
|
||||
record.get("id").and_then(Value::as_str) == Some(target.id.as_str())
|
||||
}) {
|
||||
let start = target_index.saturating_sub(target.before_lines.unwrap_or(0));
|
||||
let end = (target_index + target.after_lines.unwrap_or(0) + 1).min(records.len());
|
||||
records = records.into_iter().skip(start).take(end - start).collect();
|
||||
} else {
|
||||
records = vec![(
|
||||
serde_json::json!({
|
||||
"kind": "notice",
|
||||
"noticeType": "targetNotFound",
|
||||
"target": {
|
||||
"id": target.id,
|
||||
"beforeLines": target.before_lines,
|
||||
"afterLines": target.after_lines,
|
||||
},
|
||||
"message": format!("No inspect records matched target `{}`.", target.id),
|
||||
}),
|
||||
None,
|
||||
)];
|
||||
}
|
||||
}
|
||||
|
||||
let mut lines = Vec::new();
|
||||
let mut omitted_lines = 0usize;
|
||||
let mut omitted_chars = 0usize;
|
||||
for line in records.into_iter().map(|(record, _)| record.to_string()) {
|
||||
let separator_len = usize::from(!lines.is_empty());
|
||||
if let Some(max_chars) = args.max_chars
|
||||
&& lines.iter().map(String::len).sum::<usize>() + separator_len + line.len() > max_chars
|
||||
{
|
||||
omitted_lines += 1;
|
||||
omitted_chars += line.len();
|
||||
continue;
|
||||
}
|
||||
lines.push(line);
|
||||
}
|
||||
if omitted_lines > 0 {
|
||||
lines.push(
|
||||
serde_json::json!({
|
||||
"kind": "notice",
|
||||
"noticeType": "truncation",
|
||||
"maxChars": args.max_chars,
|
||||
"omittedLines": omitted_lines,
|
||||
"omittedChars": omitted_chars,
|
||||
"message": format!(
|
||||
"Truncated inspect output by omitting {omitted_lines} lines. Increase maxChars or narrow the filter."
|
||||
),
|
||||
})
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
lines.join("\n")
|
||||
}
|
||||
|
||||
fn legacy_target_matches(target_id: &str, record: &Value, slide_id: Option<&str>) -> bool {
|
||||
record.get("id").and_then(Value::as_str) == Some(target_id) || slide_id == Some(target_id)
|
||||
}
|
||||
|
||||
fn add_text_metadata(record: &mut Value, text: &str) {
|
||||
record["textPreview"] = Value::String(text.replace('\n', " | "));
|
||||
record["textChars"] = Value::from(text.chars().count());
|
||||
record["textLines"] = Value::from(text.lines().count());
|
||||
}
|
||||
|
||||
fn normalize_element_lookup_id(element_id: &str) -> &str {
|
||||
element_id
|
||||
.split_once('/')
|
||||
.map(|(_, normalized)| normalized)
|
||||
.unwrap_or(element_id)
|
||||
}
|
||||
|
||||
fn resolve_anchor(
|
||||
document: &PresentationDocument,
|
||||
id: &str,
|
||||
action: &str,
|
||||
) -> Result<Value, PresentationArtifactError> {
|
||||
if id == format!("pr/{}", document.artifact_id) {
|
||||
return Ok(serde_json::json!({
|
||||
"kind": "deck",
|
||||
"id": id,
|
||||
"artifactId": document.artifact_id,
|
||||
"name": document.name,
|
||||
"slideCount": document.slides.len(),
|
||||
"styleIds": document
|
||||
.named_text_styles()
|
||||
.iter()
|
||||
.map(|style| format!("st/{}", style.name))
|
||||
.collect::<Vec<_>>(),
|
||||
"activeSlideIndex": document.active_slide_index,
|
||||
"activeSlideId": document.active_slide_index.and_then(|index| document.slides.get(index)).map(|slide| format!("sl/{}", slide.slide_id)),
|
||||
}));
|
||||
}
|
||||
if let Some(style_name) = id.strip_prefix("st/") {
|
||||
let named_style = document
|
||||
.named_text_styles()
|
||||
.into_iter()
|
||||
.find(|style| style.name == style_name)
|
||||
.ok_or_else(|| PresentationArtifactError::UnsupportedFeature {
|
||||
action: action.to_string(),
|
||||
message: format!("unknown style id `{id}`"),
|
||||
})?;
|
||||
return Ok(named_text_style_to_json(&named_style, "st"));
|
||||
}
|
||||
|
||||
for (slide_index, slide) in document.slides.iter().enumerate() {
|
||||
let slide_id = format!("sl/{}", slide.slide_id);
|
||||
if id == slide_id {
|
||||
return Ok(serde_json::json!({
|
||||
"kind": "slide",
|
||||
"id": slide_id,
|
||||
"slide": slide_index + 1,
|
||||
"slideIndex": slide_index,
|
||||
"isActive": document.active_slide_index == Some(slide_index),
|
||||
"layoutId": slide.layout_id,
|
||||
"notesId": (!slide.notes.text.is_empty()).then(|| format!("nt/{}", slide.slide_id)),
|
||||
"elementIds": slide.elements.iter().map(|element| {
|
||||
let prefix = match element {
|
||||
PresentationElement::Text(_) | PresentationElement::Shape(_) => "sh",
|
||||
PresentationElement::Connector(_) => "cn",
|
||||
PresentationElement::Image(_) => "im",
|
||||
PresentationElement::Table(_) => "tb",
|
||||
PresentationElement::Chart(_) => "ch",
|
||||
};
|
||||
format!("{prefix}/{}", element.element_id())
|
||||
}).collect::<Vec<_>>(),
|
||||
}));
|
||||
}
|
||||
let notes_id = format!("nt/{}", slide.slide_id);
|
||||
if id == notes_id {
|
||||
let mut record = serde_json::json!({
|
||||
"kind": "notes",
|
||||
"id": notes_id,
|
||||
"slide": slide_index + 1,
|
||||
"slideIndex": slide_index,
|
||||
"visible": slide.notes.visible,
|
||||
"text": slide.notes.text,
|
||||
});
|
||||
add_text_metadata(&mut record, &slide.notes.text);
|
||||
record["richText"] = rich_text_to_proto(&slide.notes.text, &slide.notes.rich_text);
|
||||
return Ok(record);
|
||||
}
|
||||
if let Some(range_id) = id.strip_prefix("tr/")
|
||||
&& let Some(record) = slide
|
||||
.notes
|
||||
.rich_text
|
||||
.ranges
|
||||
.iter()
|
||||
.find(|range| range.range_id == range_id)
|
||||
.map(|range| {
|
||||
let mut record = text_range_to_proto(&slide.notes.text, range);
|
||||
record["kind"] = Value::String("textRange".to_string());
|
||||
record["id"] = Value::String(id.to_string());
|
||||
record["slide"] = Value::from(slide_index + 1);
|
||||
record["slideIndex"] = Value::from(slide_index);
|
||||
record["hostAnchor"] = Value::String(notes_id.clone());
|
||||
record["hostKind"] = Value::String("notes".to_string());
|
||||
record
|
||||
})
|
||||
{
|
||||
return Ok(record);
|
||||
}
|
||||
for element in &slide.elements {
|
||||
let mut record = match element {
|
||||
PresentationElement::Text(text) => {
|
||||
let mut record = serde_json::json!({
|
||||
"kind": "textbox",
|
||||
"id": format!("sh/{}", text.element_id),
|
||||
"elementId": text.element_id,
|
||||
"slide": slide_index + 1,
|
||||
"slideIndex": slide_index,
|
||||
"text": text.text,
|
||||
"textStyle": text_style_to_proto(&text.style),
|
||||
"richText": rich_text_to_proto(&text.text, &text.rich_text),
|
||||
"bbox": [text.frame.left, text.frame.top, text.frame.width, text.frame.height],
|
||||
"bboxUnit": "points",
|
||||
});
|
||||
add_text_metadata(&mut record, &text.text);
|
||||
record
|
||||
}
|
||||
PresentationElement::Shape(shape) => {
|
||||
let mut record = serde_json::json!({
|
||||
"kind": if shape.text.is_some() { "textbox" } else { "shape" },
|
||||
"id": format!("sh/{}", shape.element_id),
|
||||
"elementId": shape.element_id,
|
||||
"slide": slide_index + 1,
|
||||
"slideIndex": slide_index,
|
||||
"geometry": format!("{:?}", shape.geometry),
|
||||
"text": shape.text,
|
||||
"textStyle": text_style_to_proto(&shape.text_style),
|
||||
"richText": shape
|
||||
.text
|
||||
.as_ref()
|
||||
.zip(shape.rich_text.as_ref())
|
||||
.map(|(text, rich_text)| rich_text_to_proto(text, rich_text))
|
||||
.unwrap_or(Value::Null),
|
||||
"rotation": shape.rotation_degrees,
|
||||
"flipHorizontal": shape.flip_horizontal,
|
||||
"flipVertical": shape.flip_vertical,
|
||||
"bbox": [shape.frame.left, shape.frame.top, shape.frame.width, shape.frame.height],
|
||||
"bboxUnit": "points",
|
||||
});
|
||||
if let Some(text) = &shape.text {
|
||||
add_text_metadata(&mut record, text);
|
||||
}
|
||||
record
|
||||
}
|
||||
PresentationElement::Connector(connector) => serde_json::json!({
|
||||
"kind": "connector",
|
||||
"id": format!("cn/{}", connector.element_id),
|
||||
"elementId": connector.element_id,
|
||||
"slide": slide_index + 1,
|
||||
"slideIndex": slide_index,
|
||||
"connectorType": format!("{:?}", connector.connector_type),
|
||||
"start": [connector.start.left, connector.start.top],
|
||||
"end": [connector.end.left, connector.end.top],
|
||||
"lineStyle": format!("{:?}", connector.line_style),
|
||||
"label": connector.label,
|
||||
}),
|
||||
PresentationElement::Image(image) => serde_json::json!({
|
||||
"kind": "image",
|
||||
"id": format!("im/{}", image.element_id),
|
||||
"elementId": image.element_id,
|
||||
"slide": slide_index + 1,
|
||||
"slideIndex": slide_index,
|
||||
"alt": image.alt_text,
|
||||
"prompt": image.prompt,
|
||||
"fit": format!("{:?}", image.fit_mode),
|
||||
"rotation": image.rotation_degrees,
|
||||
"flipHorizontal": image.flip_horizontal,
|
||||
"flipVertical": image.flip_vertical,
|
||||
"crop": image.crop.map(|(left, top, right, bottom)| serde_json::json!({
|
||||
"left": left,
|
||||
"top": top,
|
||||
"right": right,
|
||||
"bottom": bottom,
|
||||
})),
|
||||
"isPlaceholder": image.is_placeholder,
|
||||
"lockAspectRatio": image.lock_aspect_ratio,
|
||||
"bbox": [image.frame.left, image.frame.top, image.frame.width, image.frame.height],
|
||||
"bboxUnit": "points",
|
||||
}),
|
||||
PresentationElement::Table(table) => serde_json::json!({
|
||||
"kind": "table",
|
||||
"id": format!("tb/{}", table.element_id),
|
||||
"elementId": table.element_id,
|
||||
"slide": slide_index + 1,
|
||||
"slideIndex": slide_index,
|
||||
"rows": table.rows.len(),
|
||||
"cols": table.rows.iter().map(std::vec::Vec::len).max().unwrap_or(0),
|
||||
"columnWidths": table.column_widths,
|
||||
"rowHeights": table.row_heights,
|
||||
"style": table.style,
|
||||
"styleOptions": table_style_options_to_proto(&table.style_options),
|
||||
"borders": table.borders.as_ref().map(table_borders_to_proto),
|
||||
"rightToLeft": table.right_to_left,
|
||||
"cellTextStyles": table
|
||||
.rows
|
||||
.iter()
|
||||
.map(|row| row.iter().map(|cell| text_style_to_proto(&cell.text_style)).collect::<Vec<_>>())
|
||||
.collect::<Vec<_>>(),
|
||||
"rowsData": table
|
||||
.rows
|
||||
.iter()
|
||||
.map(|row| row.iter().map(table_cell_to_proto).collect::<Vec<_>>())
|
||||
.collect::<Vec<_>>(),
|
||||
"bbox": [table.frame.left, table.frame.top, table.frame.width, table.frame.height],
|
||||
"bboxUnit": "points",
|
||||
}),
|
||||
PresentationElement::Chart(chart) => serde_json::json!({
|
||||
"kind": "chart",
|
||||
"id": format!("ch/{}", chart.element_id),
|
||||
"elementId": chart.element_id,
|
||||
"slide": slide_index + 1,
|
||||
"slideIndex": slide_index,
|
||||
"chartType": format!("{:?}", chart.chart_type),
|
||||
"title": chart.title,
|
||||
"styleIndex": chart.style_index,
|
||||
"hasLegend": chart.has_legend,
|
||||
"legend": chart.legend.as_ref().map(chart_legend_to_proto),
|
||||
"xAxis": chart.x_axis.as_ref().map(chart_axis_to_proto),
|
||||
"yAxis": chart.y_axis.as_ref().map(chart_axis_to_proto),
|
||||
"dataLabels": chart.data_labels.as_ref().map(chart_data_labels_to_proto),
|
||||
"chartFill": chart.chart_fill,
|
||||
"plotAreaFill": chart.plot_area_fill,
|
||||
"series": chart
|
||||
.series
|
||||
.iter()
|
||||
.map(|series| serde_json::json!({
|
||||
"name": series.name,
|
||||
"values": series.values,
|
||||
"categories": series.categories,
|
||||
"xValues": series.x_values,
|
||||
"fill": series.fill,
|
||||
"stroke": series.stroke.as_ref().map(stroke_to_proto),
|
||||
"marker": series.marker.as_ref().map(chart_marker_to_proto),
|
||||
"dataLabelOverrides": series
|
||||
.data_label_overrides
|
||||
.iter()
|
||||
.map(chart_data_label_override_to_proto)
|
||||
.collect::<Vec<_>>(),
|
||||
}))
|
||||
.collect::<Vec<_>>(),
|
||||
"bbox": [chart.frame.left, chart.frame.top, chart.frame.width, chart.frame.height],
|
||||
"bboxUnit": "points",
|
||||
}),
|
||||
};
|
||||
if let Some(hyperlink) = match element {
|
||||
PresentationElement::Text(text) => text.hyperlink.as_ref(),
|
||||
PresentationElement::Shape(shape) => shape.hyperlink.as_ref(),
|
||||
PresentationElement::Connector(_)
|
||||
| PresentationElement::Image(_)
|
||||
| PresentationElement::Table(_)
|
||||
| PresentationElement::Chart(_) => None,
|
||||
} {
|
||||
record["hyperlink"] = hyperlink.to_json();
|
||||
}
|
||||
if let PresentationElement::Shape(shape) = element
|
||||
&& let Some(stroke) = &shape.stroke
|
||||
{
|
||||
record["stroke"] = serde_json::json!({
|
||||
"color": stroke.color,
|
||||
"width": stroke.width,
|
||||
"style": stroke.style.as_api_str(),
|
||||
});
|
||||
}
|
||||
if let Some(placeholder) = match element {
|
||||
PresentationElement::Text(text) => text.placeholder.as_ref(),
|
||||
PresentationElement::Shape(shape) => shape.placeholder.as_ref(),
|
||||
PresentationElement::Image(image) => image.placeholder.as_ref(),
|
||||
PresentationElement::Connector(_)
|
||||
| PresentationElement::Table(_)
|
||||
| PresentationElement::Chart(_) => None,
|
||||
} {
|
||||
record["placeholder"] = Value::String(placeholder.placeholder_type.clone());
|
||||
record["placeholderName"] = Value::String(placeholder.name.clone());
|
||||
record["placeholderIndex"] =
|
||||
placeholder.index.map(Value::from).unwrap_or(Value::Null);
|
||||
}
|
||||
if record.get("id").and_then(Value::as_str) == Some(id) {
|
||||
return Ok(record);
|
||||
}
|
||||
if let Some(range_id) = id.strip_prefix("tr/") {
|
||||
match element {
|
||||
PresentationElement::Text(text) => {
|
||||
if let Some(range) =
|
||||
text.rich_text.ranges.iter().find(|range| range.range_id == range_id)
|
||||
{
|
||||
let mut range_record = text_range_to_proto(&text.text, range);
|
||||
range_record["kind"] = Value::String("textRange".to_string());
|
||||
range_record["id"] = Value::String(id.to_string());
|
||||
range_record["slide"] = Value::from(slide_index + 1);
|
||||
range_record["slideIndex"] = Value::from(slide_index);
|
||||
range_record["hostAnchor"] =
|
||||
Value::String(format!("sh/{}", text.element_id));
|
||||
range_record["hostKind"] = Value::String("textbox".to_string());
|
||||
return Ok(range_record);
|
||||
}
|
||||
}
|
||||
PresentationElement::Shape(shape) => {
|
||||
if let Some((text, rich_text)) =
|
||||
shape.text.as_ref().zip(shape.rich_text.as_ref())
|
||||
&& let Some(range) =
|
||||
rich_text.ranges.iter().find(|range| range.range_id == range_id)
|
||||
{
|
||||
let mut range_record = text_range_to_proto(text, range);
|
||||
range_record["kind"] = Value::String("textRange".to_string());
|
||||
range_record["id"] = Value::String(id.to_string());
|
||||
range_record["slide"] = Value::from(slide_index + 1);
|
||||
range_record["slideIndex"] = Value::from(slide_index);
|
||||
range_record["hostAnchor"] =
|
||||
Value::String(format!("sh/{}", shape.element_id));
|
||||
range_record["hostKind"] = Value::String("textbox".to_string());
|
||||
return Ok(range_record);
|
||||
}
|
||||
}
|
||||
PresentationElement::Table(table) => {
|
||||
for (row_index, row) in table.rows.iter().enumerate() {
|
||||
for (column_index, cell) in row.iter().enumerate() {
|
||||
if let Some(range) = cell
|
||||
.rich_text
|
||||
.ranges
|
||||
.iter()
|
||||
.find(|range| range.range_id == range_id)
|
||||
{
|
||||
let mut range_record = text_range_to_proto(&cell.text, range);
|
||||
range_record["kind"] = Value::String("textRange".to_string());
|
||||
range_record["id"] = Value::String(id.to_string());
|
||||
range_record["slide"] = Value::from(slide_index + 1);
|
||||
range_record["slideIndex"] = Value::from(slide_index);
|
||||
range_record["hostAnchor"] = Value::String(format!(
|
||||
"tb/{}#cell/{row_index}/{column_index}",
|
||||
table.element_id
|
||||
));
|
||||
range_record["hostKind"] =
|
||||
Value::String("tableCell".to_string());
|
||||
return Ok(range_record);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
PresentationElement::Connector(_)
|
||||
| PresentationElement::Image(_)
|
||||
| PresentationElement::Chart(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(thread_id) = id.strip_prefix("th/")
|
||||
&& let Some(thread) = document
|
||||
.comment_threads
|
||||
.iter()
|
||||
.find(|thread| thread.thread_id == thread_id)
|
||||
{
|
||||
let mut record = comment_thread_to_proto(thread);
|
||||
record["id"] = Value::String(id.to_string());
|
||||
return Ok(record);
|
||||
}
|
||||
|
||||
for layout in &document.layouts {
|
||||
let layout_id = format!("ly/{}", layout.layout_id);
|
||||
if id == layout_id {
|
||||
return Ok(serde_json::json!({
|
||||
"kind": "layout",
|
||||
"id": layout_id,
|
||||
"layoutId": layout.layout_id,
|
||||
"name": layout.name,
|
||||
"type": match layout.kind {
|
||||
LayoutKind::Layout => "layout",
|
||||
LayoutKind::Master => "master",
|
||||
},
|
||||
"parentLayoutId": layout.parent_layout_id,
|
||||
"placeholders": layout_placeholder_list(document, &layout.layout_id, action)?,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
Err(PresentationArtifactError::UnsupportedFeature {
|
||||
action: action.to_string(),
|
||||
message: format!("unknown resolve id `{id}`"),
|
||||
})
|
||||
}
|
||||
3203
codex-rs/artifact-presentation/src/presentation_artifact/manager.rs
Normal file
3203
codex-rs/artifact-presentation/src/presentation_artifact/manager.rs
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user